Overview

In this module, we will explore dictionary-based word count approaches text analysis measurement.

We will be working with U.S. House floor speeches spanning June 1998 through July 1999. More specifically, we will:

  1. investigate variations in sentiment as a function of political party
  2. explore sentiment dynamics in the context of President Bill Clinton’s impeachment.

Environment Preparation

# Load packages 
library(pacman)
p_load(readr, dplyr, tidyr, ggplot2, jtools, 
       knitr, reshape2, jsonlite, lubridate)

Data Preparation

In the preprocessing module (../notebooks/tdta_preprocessing.Rmd), we saved a tidy format data.frame containing our corpus. We’ll use that as a starting point for our work in this module.


dat_tidy <- readRDS('../data/tdta_clean_house_data_tidy.RDS')

dat_tidy

In this data.frame, words are stored in the cells of the column word. Accordingly, an entire document, identified by the unique document identifier doc_num, is stored across multiple rows. This data.frame also contains metadata like the name of the speaker associated with a given document, as well as the the speaker’s party, district, and State.

Train/Test Split

If you have enough data, there’s really never any reason to not separate your data into a training set and testing set. Of course, determing how much data is enough can be tricky, because you don’t want to create a situation where you are underpowered or unable to estimate a target parameter with sufficient precision.

However, treating documents as our units of observation, we have an $N = $ 35,959, which is certainly large enough for splitting.


doc_ids <- unique(dat_tidy$doc_num) # Get document IDs

n_docs = .50 * length(doc_ids) # Calculate number of documents to sample

set.seed(5435) # set seed for reproducibility

doc_ids_test = sample(doc_ids, n_docs) # sample document IDs for test data

dat_tidy.train <- dat_tidy %>%
  filter(doc_num %!in% doc_ids_test) # Select documents for training

dat_tidy.test <- dat_tidy %>%
  filter(doc_num %in% doc_ids_test) # Select documents for test

Conducting Word counts

Introduction to dictionaries

To conduct word count analyses, you need a dictionary or lexicon that specifies the words associated with your target construct(s).

To start, we’ll work with the NRC sentiment dictionary, which is one of three sentiment dictionaries packaged with tidytext:

  1. AFINN
  2. bing
  3. nrc

To access the NRC dictionary, we’ll use the get_sentiments function to store the NRC dictionary in an object called nrc_sent.

nrc_sent <- get_sentiments('nrc')
nrc_sent

nrc_sent contains two columns, word, which contains the words in the dictionary, and sentiment, which specifies the sentiment label associated with the word.

nrc_sent %>%
  count(sentiment)

The NRC dictionary contains 10 sentiment categories and each of these categories have varying numbers of words associated with them.

Let’s take a glance at first words in each category by spreading our tidy data.frame:

nrc_sent %>%
  group_by(sentiment) %>% 
  mutate(temp_id = row_number()) %>% # Create a temporary ID to weight top_n by
  top_n(n = -50, wt=temp_id) %>%     # Get first 50 items in each group 
  mutate(temp_id = row_number()) %>% # Create a temporary unique ID for each word in each group
  ungroup() %>%
  pivot_wider(names_from = sentiment, values_from = word) %>% # Spread our data
  select(-temp_id)

Glancing at these words, it’s clear that words are repeated in some categories.


A cautionary note on word count validity

One of the greatest strengths of dictionary-based text measurement methods is that they allow you to precisely define the construct you are interested in. This works extremely well when you are interested in specific words or types of words.

For example, if you are interested in function words, then it would never make sense to use anything other than a dictionary-based approach. Similarly, if you are truly interested in the usage of specific positive or negative words, then, again, it probably wouldn’t make sense to use anything other than a dictionary approach.

In these examples, there is a 1:1 relationship between the target construct and the operationalization. However, this 1:1 relationship is difficult to maintain for more abstract constructs, like “positive sentiment” or “negative sentiment”. In such cases, you (or someone else) has to decide which words evoke “positive sentiment” or “negative sentiment”.

Further, we are often interested in expressions of meaning that may operate above the word level. For instance, consider the following example:

`Let’s just say…I didn’t love it’

Most dictionary-based word count methods would estimate the sentiment expressed in this sentence as “positive” because of the token love. However, considering the entire context of this example, we can infer that the most likely sentiment is probably “negative”.

In sum, dictionary-based word count approaches can be quite powerful; however, they have two notable shortcomings:

  1. Dependence on dictionary validity
  2. Cannot account for context

This does not mean that you shouldn’t use dictionary-based word count methods. However, it does mean that you should keep these short comings in mind. And, even better, you should try to account for them through validation.

tidytext word count sentiment analysis

In principle, tidytext makes dictionary-based word count sentiment analysis quite simple.

To count the words we can just:

  1. Conduct an inner_join between our data and our sentiment dictionary
  2. Count the matches

We’ll also divide the number of matches for each sentiment domain by the total number of words in our corpus. This will tell us the proportion associated with each sentiment domain.


total_words = nrow(dat_tidy.train)

dat_tidy.train %>%
  inner_join(nrc_sent) %>%
  count(sentiment) %>%
  mutate(prop = n/total_words) %>%
  arrange(desc(prop))

Affective sentiment by Political Party

We can also subset our data in order to ask more specific questions. For instance, we can easily estimate sentiment proportions for Democrats and Republicans.

Overall, it looks like there is very little mean sentiment variation between Republicans and Democrats. However, we’ve collapsed across documents. To get a better idea of how expressions of affective sentiment vary across Parties, let’s visualize the distribution of sentiment in documents

What if we look at the document Ns of sentiment words instead of proportions?


Dynamics in affective sentiment by political party

Clearly, there isn’t much marginal between group variation in affective sentiment in this dataset. However, maybe there are interesting effects operating at other levels!

Let’s examine temporal variation in affective sentiment between parties. To do this, we will take a similar approach, but instead of counting the sentiment in each document, we’ll count the sentiment on each observed day.

First, however, let’s glance at the distribution of documents across time. For reference, let’s also add vertical lines to indicate the dates on which Clinton’s impeachment was iniated and voted on.


sent_time <- dat_tidy.train.sent %>%
  filter(Party !='Independent') %>%
  distinct(doc_num, .keep_all = T) %>%
  count(date, Party) %>%
  ggplot(aes(y = n, x = date, color=Party)) + 
  geom_line(alpha=.5) + 
  facet_wrap(Party ~. , ncol=1) +
  theme_apa() +
  geom_vline(xintercept=as.numeric(as_datetime('1998-10-08')), linetype=2, alpha=.25) +
  geom_vline(xintercept=as.numeric(as_datetime('1998-12-19')), linetype=2, alpha=.25) +
  geom_point() +
  theme_apa() +
  ggtitle('N of documents across time by party') +
  ylab('N') + 
  xlab('Date') +
  labs(subtitle = "Veritical lines indicate initiation of impeachment and decision to impeach")
  

ggplotly(sent_time)

NA
NA

Clearly, there is substantial variation in the number of documents (i.e. speeches given by individual speakers) across time.


Now, let’s plot sentiment across time by party.


dat_tidy.train.sent %>%
  filter(Party !='Independent') %>%
  count(total_words, Party, date, sentiment) %>%
  mutate(prop = n/total_words) %>%
  ggplot(aes(y = prop, x = date, color=Party)) + 
  facet_wrap(sentiment~Party, ncol=4) + 
  scale_colour_manual(values = c("blue", "red")) +
  theme() + 
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) + 
  ggtitle('Document-level proportions of sentiment words by party') + 
  xlab('Party') + 
  ylab('N') + 
  geom_smooth(color='black') +
  geom_vline(xintercept=as.numeric(as_datetime('1998-10-08')), linetype=2) +
  geom_vline(xintercept=as.numeric(as_datetime('1998-12-19')), linetype=2) +
  geom_point(alpha=.25) 


Hypothesis testing with word counts

dat_tidy.train.speaker <- dat_tidy.train %>%
  group_by(Party, speaker) %>%
  mutate(speaker_total_words = n()) %>%
  ungroup() %>%
  inner_join(nrc_sent) %>%
  count(Party, speaker, speaker_total_words, date, sentiment) %>%
  group_by(speaker, sentiment) %>%
  mutate(speaker_sent_means = mean(n), 
         speaker_sent_cntr = n - speaker_sent_means)
Joining, by = "word"

dat_tidy.train.speaker <- dat_tidy.train %>%
  filter(Party != 'Independent') %>%
  group_by(Party, speaker, date) %>%
  mutate(speaker_day_n = n()) %>%
  ungroup() %>%
  inner_join(nrc_sent) %>%
  count(Party, speaker, date, speaker_day_n, sentiment) %>%
  mutate(speaker_day_sent_prop = n/speaker_day_n)
Joining, by = "word"
# Create a date range from the min/max dates in our training data
date_grid <- tibble(date = seq(min(dat_tidy.train.speaker$date), 
                               max(dat_tidy.train.speaker$date), by='days')) %>%
  mutate(date_int = row_number(), # Associate each date with an integer 
         date_int_scaled = date_int/100)

dat_tidy.train.speaker <- dat_tidy.train.speaker %>%
  left_join(date_grid) %>%
  mutate(date_int_scaled = date_int/100, 
         impeachment_1 = ifelse(date == as_datetime('1998-10-08'), 1, 0),
         impeachment_2 = ifelse(date == as_datetime('1998-12-19'), 1, 0))
Joining, by = "date"
  

train.speaker.negative.m1 <- dat_tidy.train.speaker %>%
  filter(sentiment=='negative') %>%
  lmer(speaker_day_sent_prop ~ 1 + Party*impeachment_1 + Party*impeachment_2  + (1  | speaker) + (1 + Party | date_int), data=.)

summary(train.speaker.negative.m1)
Linear mixed model fit by REML ['lmerMod']
Formula: speaker_day_sent_prop ~ 1 + Party * impeachment_1 + Party * impeachment_2 +  
    (1 | speaker) + (1 + Party | date_int)
   Data: .

REML criterion at convergence: -34881.5

Scaled residuals: 
    Min      1Q  Median      3Q     Max 
-2.7629 -0.6398 -0.1575  0.4602  8.0026 

Random effects:
 Groups   Name            Variance  Std.Dev.  Corr 
 speaker  (Intercept)     2.464e-05 0.0049634      
 date_int (Intercept)     2.916e-05 0.0054003      
          PartyRepublican 7.967e-07 0.0008926 -0.42
 Residual                 2.760e-04 0.0166138      
Number of obs: 6630, groups:  speaker, 638; date_int, 144

Fixed effects:
                                Estimate Std. Error t value
(Intercept)                    0.0307114  0.0006638  46.265
PartyRepublican               -0.0001991  0.0006508  -0.306
impeachment_1                  0.0039442  0.0058847   0.670
impeachment_2                  0.0218401  0.0063649   3.431
PartyRepublican:impeachment_1  0.0004287  0.0033720   0.127
PartyRepublican:impeachment_2 -0.0002016  0.0048421  -0.042

Correlation of Fixed Effects:
            (Intr) PrtyRp impc_1 impc_2 PrR:_1
PartyRpblcn -0.522                            
impechmnt_1 -0.081  0.026                     
impechmnt_2 -0.079  0.028  0.010              
PrtyRpbl:_1  0.045 -0.081 -0.365 -0.007       
PrtyRpbl:_2  0.036 -0.064 -0.005 -0.426  0.013
train.speaker.negative.m1.pred %>%
  left_join(date_grid) %>%
  ggplot(aes(x = date, y = preds, color=Party)) + 
  geom_line() + 
  geom_point(aes(y = preds), alpha=.25) +
  theme_apa() + 
  scale_colour_manual(values = c("blue", "red")) + 
  theme_apa() +
  geom_vline(xintercept=as.numeric(as_datetime('1998-10-08')), linetype=2, alpha=.25) +
  geom_vline(xintercept=as.numeric(as_datetime('1998-12-19')), linetype=2, alpha=.25) +
  ggtitle('Daily expected proportion of negative language for an average speaker' ) +
  ylab('Speaker proportion negative language') + 
  xlab('Date')
Joining, by = c("date_int", "date", "date_int_scaled")

Disgust



train.speaker.disgust.m1 <- dat_tidy.train.speaker %>%
  filter(sentiment=='disgust') %>%
  lmer(speaker_day_sent_prop ~ 1 + Party*impeachment_1 + Party*impeachment_2  + (1  | speaker) + (1 + Party | date_int), data=.)

summary(train.speaker.disgust.m1)
Linear mixed model fit by REML ['lmerMod']
Formula: speaker_day_sent_prop ~ 1 + Party * impeachment_1 + Party * impeachment_2 +  
    (1 | speaker) + (1 + Party | date_int)
   Data: .

REML criterion at convergence: -37281.3

Scaled residuals: 
    Min      1Q  Median      3Q     Max 
-2.1327 -0.6168 -0.2363  0.3417 11.8113 

Random effects:
 Groups   Name            Variance  Std.Dev.  Corr
 speaker  (Intercept)     3.769e-06 0.0019413     
 date_int (Intercept)     4.166e-06 0.0020410     
          PartyRepublican 1.502e-07 0.0003876 0.15
 Residual                 5.562e-05 0.0074581     
Number of obs: 5428, groups:  speaker, 576; date_int, 141

Fixed effects:
                                Estimate Std. Error t value
(Intercept)                    9.484e-03  2.782e-04  34.086
PartyRepublican                3.560e-04  2.948e-04   1.207
impeachment_1                  1.546e-03  2.342e-03   0.660
impeachment_2                  6.181e-03  2.556e-03   2.419
PartyRepublican:impeachment_1  2.194e-04  1.651e-03   0.133
PartyRepublican:impeachment_2 -3.549e-05  2.214e-03  -0.016

Correlation of Fixed Effects:
            (Intr) PrtyRp impc_1 impc_2 PrR:_1
PartyRpblcn -0.516                            
impechmnt_1 -0.085  0.029                     
impechmnt_2 -0.085  0.034  0.011              
PrtyRpbl:_1  0.043 -0.088 -0.301 -0.008       
PrtyRpbl:_2  0.041 -0.078 -0.006 -0.391  0.014
train.speaker.disgust.m1.pred <- dat_tidy.train.speaker.pred_grid %>%
  mutate(preds = predict(train.speaker.disgust.m1, newdata=dat_tidy.train.speaker.pred_grid, allow.new.levels=T))
  


train.speaker.disgust.m1.pred %>%
  left_join(date_grid) %>%
  ggplot(aes(x = date, y = preds, color=Party)) + 
  geom_line() + 
  geom_point(aes(y = preds), alpha=.25) +
  theme_apa() + 
  scale_colour_manual(values = c("blue", "red")) + 
  theme_apa() +
  geom_vline(xintercept=as.numeric(as_datetime('1998-10-08')), linetype=2, alpha=.25) +
  geom_vline(xintercept=as.numeric(as_datetime('1998-12-19')), linetype=2, alpha=.25) +
  ggtitle('Daily expected proportion of disgust language for an average speaker' ) +
  ylab('Speaker proportion disgust language') + 
  xlab('Date')
Joining, by = c("date_int", "date", "date_int_scaled")




train.speaker.all.m1 <- dat_tidy.train.speaker %>%
  lmer(speaker_day_sent_prop ~ 1 + Party*impeachment_1 + Party*impeachment_2  + (1 | speaker) + (1 + Party | date_int) + (1 + impeachment_1  + impeachment_2 | sentiment), data=.)

summary(train.speaker.all.m1)


dat_tidy.train.speaker.pred_grid.all_sent <- expand.grid(Party = unique(dat_tidy.train.speaker$Party), 
            speaker = 'new_speaker', 
            date_int = unique(dat_tidy.train.speaker$date_int),
            sentiment=unique(dat_tidy.train.speaker$sentiment))


dat_tidy.train.speaker.pred_grid.all_sent <- dat_tidy.train.speaker.pred_grid.all_sent %>%
  left_join(date_grid) %>%
  mutate(impeachment_1 = ifelse(date == as_datetime('1998-10-08'), 1, 0),
         impeachment_2 = ifelse(date == as_datetime('1998-12-19'), 1, 0))


train.speaker.all.m1.pred <- dat_tidy.train.speaker.pred_grid.all_sent %>%
  mutate(preds = predict(train.speaker.all.m1, 
                         newdata=dat_tidy.train.speaker.pred_grid.all_sent, 
                         allow.new.levels=T))
  
train.speaker.all.m1.pred %>%
  left_join(date_grid) %>%
  ggplot(aes(x = date, y = preds, color=Party)) + 
  geom_line() + 
  geom_point(aes(y = preds), alpha=.25) +
  theme_apa() + 
  scale_colour_manual(values = c("blue", "red")) + 
  theme_apa() +
  geom_vline(xintercept=as.numeric(as_datetime('1998-10-08')), linetype=2, alpha=.25) +
  geom_vline(xintercept=as.numeric(as_datetime('1998-12-19')), linetype=2, alpha=.25) +
  ggtitle('Daily expected proportion of disgust language for an average speaker' ) +
  ylab('Speaker proportion disgust language') + 
  xlab('Date') + facet_wrap(sentiment~., ncol=2)
Joining, by = c("date_int", "date", "date_int_scaled")

train.speaker.all.m1.pred %>%
  left_join(date_grid) %>%
  ggplot(aes(x = date, y = preds, color=Party)) + 
  geom_line() + 
  geom_point(aes(y = preds), alpha=.25) +
  theme_apa() + 
  scale_colour_manual(values = c("blue", "red")) + 
  theme_apa() +
  geom_vline(xintercept=as.numeric(as_datetime('1998-10-08')), linetype=2, alpha=.25) +
  geom_vline(xintercept=as.numeric(as_datetime('1998-12-19')), linetype=2, alpha=.25) +
  ggtitle('Daily expected proportion of disgust language for an average speaker' ) +
  ylab('Speaker proportion disgust language') + 
  xlab('Date') + facet_wrap(sentiment~., ncol=2, scales='free_y')
Joining, by = c("date_int", "date", "date_int_scaled")

sjPlot::plot_model(train.speaker.all.m1, type='re')[3]
[[1]]

Confirmation


dat_tidy.test.speaker <- dat_tidy.test %>%
  group_by(Party, speaker) %>%
  mutate(speaker_total_words = n()) %>%
  ungroup() %>%
  inner_join(nrc_sent) %>%
  count(Party, speaker, speaker_total_words, date, sentiment) %>%
  group_by(speaker, sentiment)
Joining, by = "word"
  

dat_tidy.test.speaker <- dat_tidy.test %>%
  filter(Party != 'Independent') %>%
  group_by(Party, speaker, date) %>%
  mutate(speaker_day_n = n()) %>%
  ungroup() %>%
  inner_join(nrc_sent) %>%
  count(Party, speaker, date, speaker_day_n, sentiment) %>%
  mutate(speaker_day_sent_prop = n/speaker_day_n)
Joining, by = "word"
# Create a date range from the min/max dates in our testing data
date_grid <- tibble(date = seq(min(dat_tidy.test.speaker$date), 
                               max(dat_tidy.test.speaker$date), by='days')) %>%
  mutate(date_int = row_number(), # Associate each date with an integer 
         date_int_scaled = date_int/100)

dat_tidy.test.speaker <- dat_tidy.test.speaker %>%
  left_join(date_grid) %>%
  mutate(date_int_scaled = date_int/100, 
         impeachment_1 = ifelse(date == as_datetime('1998-10-08'), 1, 0),
         impeachment_2 = ifelse(date == as_datetime('1998-12-19'), 1, 0))
Joining, by = "date"
  

test.speaker.negative.m1 <- dat_tidy.test.speaker %>%
  filter(sentiment=='negative') %>%
  lmer(speaker_day_sent_prop ~ 1 + Party*impeachment_1 + Party*impeachment_2  + (1  | speaker) + (1 + Party | date_int), data=.)
boundary (singular) fit: see ?isSingular
summary(test.speaker.negative.m1)
Linear mixed model fit by REML ['lmerMod']
Formula: 
speaker_day_sent_prop ~ 1 + Party * impeachment_1 + Party * impeachment_2 +  
    (1 | speaker) + (1 + Party | date_int)
   Data: .

REML criterion at convergence: -34917.8

Scaled residuals: 
    Min      1Q  Median      3Q     Max 
-2.3963 -0.6268 -0.1663  0.4381 11.4546 

Random effects:
 Groups   Name            Variance  Std.Dev.  Corr 
 speaker  (Intercept)     2.261e-05 0.0047553      
 date_int (Intercept)     3.451e-05 0.0058743      
          PartyRepublican 6.270e-07 0.0007919 -1.00
 Residual                 3.021e-04 0.0173800      
Number of obs: 6743, groups:  speaker, 652; date_int, 143

Fixed effects:
                                Estimate Std. Error t value
(Intercept)                    0.0313529  0.0006947  45.134
PartyRepublican               -0.0002498  0.0006483  -0.385
impeachment_1                  0.0090151  0.0063993   1.409
impeachment_2                  0.0173639  0.0070045   2.479
PartyRepublican:impeachment_1 -0.0083012  0.0033936  -2.446
PartyRepublican:impeachment_2  0.0058701  0.0053485   1.098

Correlation of Fixed Effects:
            (Intr) PrtyRp impc_1 impc_2 PrR:_1
PartyRpblcn -0.546                            
impechmnt_1 -0.083  0.032                     
impechmnt_2 -0.078  0.031  0.009              
PrtyRpbl:_1  0.056 -0.090 -0.500 -0.008       
PrtyRpbl:_2  0.038 -0.062 -0.005 -0.506  0.014
convergence code: 0
boundary (singular) fit: see ?isSingular


dat_tidy.test.speaker.pred_grid <- expand.grid(Party = unique(dat_tidy.test.speaker$Party), 
            speaker = 'new_speaker', 
            date_int = unique(dat_tidy.test.speaker$date_int))


dat_tidy.test.speaker.pred_grid <- dat_tidy.test.speaker.pred_grid %>%
  left_join(date_grid) %>%
  mutate(impeachment_1 = ifelse(date == as_datetime('1998-10-08'), 1, 0),
         impeachment_2 = ifelse(date == as_datetime('1998-12-19'), 1, 0))
Joining, by = "date_int"
test.speaker.negative.m1.pred <- dat_tidy.test.speaker.pred_grid %>%
  mutate(preds = predict(test.speaker.negative.m1, newdata=dat_tidy.test.speaker.pred_grid, allow.new.levels=T))
  


test.speaker.negative.m1.pred %>%
  left_join(date_grid) %>%
  ggplot(aes(x = date, y = preds, color=Party)) + 
  geom_line() + 
  geom_point(aes(y = preds), alpha=.25) +
  theme_apa() + 
  scale_colour_manual(values = c("blue", "red")) + 
  theme_apa() +
  geom_vline(xintercept=as.numeric(as_datetime('1998-10-08')), linetype=2, alpha=.25) +
  geom_vline(xintercept=as.numeric(as_datetime('1998-12-19')), linetype=2, alpha=.25) +
  ggtitle('Daily expected proportion of negative language for an average speaker' ) +
  ylab('Speaker proportion negative language') + 
  xlab('Date')
Joining, by = c("date_int", "date", "date_int_scaled")

NA

Disgust



test.speaker.disgust.m1 <- dat_tidy.test.speaker %>%
  filter(sentiment=='disgust') %>%
  lmer(speaker_day_sent_prop ~ 1 + Party*impeachment_1 + Party*impeachment_2  + (1  | speaker) + (1 + Party | date_int), data=.)
boundary (singular) fit: see ?isSingular
summary(test.speaker.disgust.m1)
Linear mixed model fit by REML ['lmerMod']
Formula: 
speaker_day_sent_prop ~ 1 + Party * impeachment_1 + Party * impeachment_2 +  
    (1 | speaker) + (1 + Party | date_int)
   Data: .

REML criterion at convergence: -37469.3

Scaled residuals: 
    Min      1Q  Median      3Q     Max 
-2.7009 -0.5983 -0.2280  0.3244 11.5548 

Random effects:
 Groups   Name            Variance  Std.Dev. Corr 
 speaker  (Intercept)     5.295e-06 0.002301      
 date_int (Intercept)     4.316e-06 0.002078      
          PartyRepublican 1.797e-08 0.000134 -1.00
 Residual                 5.818e-05 0.007628      
Number of obs: 5497, groups:  speaker, 570; date_int, 143

Fixed effects:
                                Estimate Std. Error t value
(Intercept)                    0.0100217  0.0002945  34.027
PartyRepublican               -0.0002112  0.0003215  -0.657
impeachment_1                  0.0029204  0.0024163   1.209
impeachment_2                  0.0050296  0.0027192   1.850
PartyRepublican:impeachment_1 -0.0014567  0.0016049  -0.908
PartyRepublican:impeachment_2  0.0026842  0.0023911   1.123

Correlation of Fixed Effects:
            (Intr) PrtyRp impc_1 impc_2 PrR:_1
PartyRpblcn -0.566                            
impechmnt_1 -0.082  0.032                     
impechmnt_2 -0.074  0.030  0.010              
PrtyRpbl:_1  0.053 -0.089 -0.456 -0.008       
PrtyRpbl:_2  0.037 -0.065 -0.006 -0.511  0.014
convergence code: 0
boundary (singular) fit: see ?isSingular
test.speaker.disgust.m1.pred <- dat_tidy.test.speaker.pred_grid %>%
  mutate(preds = predict(test.speaker.disgust.m1, newdata=dat_tidy.test.speaker.pred_grid, allow.new.levels=T))
  


test.speaker.disgust.m1.pred %>%
  left_join(date_grid) %>%
  ggplot(aes(x = date, y = preds, color=Party)) + 
  geom_line() + 
  geom_point(aes(y = preds), alpha=.25) +
  theme_apa() + 
  scale_colour_manual(values = c("blue", "red")) + 
  theme_apa() +
  geom_vline(xintercept=as.numeric(as_datetime('1998-10-08')), linetype=2, alpha=.25) +
  geom_vline(xintercept=as.numeric(as_datetime('1998-12-19')), linetype=2, alpha=.25) +
  ggtitle('Daily expected proportion of disgust language for an average speaker' ) +
  ylab('Speaker proportion disgust language') + 
  xlab('Date')
Joining, by = c("date_int", "date", "date_int_scaled")

dat_tidy.test.speaker.pred_grid.all_sent <- dat_tidy.test.speaker.pred_grid.all_sent %>%
  left_join(date_grid) %>%
  mutate(impeachment_1 = ifelse(date == as_datetime('1998-10-08'), 1, 0),
         impeachment_2 = ifelse(date == as_datetime('1998-12-19'), 1, 0))
Joining, by = "date_int"
test.speaker.all.m1.pred %>%
  left_join(date_grid) %>%
  ggplot(aes(x = date, y = preds, color=Party)) + 
  geom_line() + 
  geom_point(aes(y = preds), alpha=.25) +
  theme_apa() + 
  scale_colour_manual(values = c("blue", "red")) + 
  theme_apa() +
  geom_vline(xintercept=as.numeric(as_datetime('1998-10-08')), linetype=2, alpha=.25) +
  geom_vline(xintercept=as.numeric(as_datetime('1998-12-19')), linetype=2, alpha=.25) +
  ggtitle('Daily expected proportion of disgust language for an average speaker' ) +
  ylab('Speaker proportion disgust language') + 
  xlab('Date') + facet_wrap(sentiment~., ncol=2)
Joining, by = c("date_int", "date", "date_int_scaled")

test.speaker.all.m1.pred %>%
  left_join(date_grid) %>%
  ggplot(aes(x = date, y = preds, color=Party)) + 
  geom_line() + 
  geom_point(aes(y = preds), alpha=.25) +
  theme_apa() + 
  scale_colour_manual(values = c("blue", "red")) + 
  theme_apa() +
  geom_vline(xintercept=as.numeric(as_datetime('1998-10-08')), linetype=2, alpha=.25) +
  geom_vline(xintercept=as.numeric(as_datetime('1998-12-19')), linetype=2, alpha=.25) +
  ggtitle('Daily expected proportion of disgust language for an average speaker' ) +
  ylab('Speaker proportion disgust language') + 
  xlab('Date') + facet_wrap(sentiment~., ncol=2, scales='free_y')
Joining, by = c("date_int", "date", "date_int_scaled")

sjPlot::plot_model(test.speaker.all.m1, type='re')[3]
[[1]]

Validating dictionaries

One of the greatest strengths of dictionary-based text measurement methods is that they allow you to precisely define the construct you are interested in. This works extremely well when you are interested in specific words or types of words.

For example, if you are interested in function words, then it would never make sense to use anything other than a dictionary-based approach. Similarly, if you are truly interested in the usage of positive or negative words, then, again, it probably wouldn’t make sense to use anything other than a dictionary approach.

However,

LS0tCnRpdGxlOiAiVGhlb3J5IERyaXZlbiBUZXh0IEFuYWx5c2lzIFdvcmtzaG9wIgpzdWJ0aXRsZTogIkRpY3Rpb25hcnkgTWV0aG9kcyAtLS0gV29yZCBjb3VudHMgXG5cblNQU1AgMjAyMCIKYXV0aG9yOiAKICBuYW1lOiAiSm9lIEhvb3ZlciAmIEJyZW5kYW4gS2VubmVkeSIKICBlbWFpbDogImpvc2VwaC5ob292ZXJAa2VsbG9nZy5ub3J0aHdlc3Rlcm4uZWR1XG5cbmJ0a2VubmVkQHVzYy5lZHUiCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgdG9jOiB5ZXMKLS0tCgoKIyBPdmVydmlldwoKSW4gdGhpcyBtb2R1bGUsIHdlIHdpbGwgZXhwbG9yZSBkaWN0aW9uYXJ5LWJhc2VkIHdvcmQgY291bnQgYXBwcm9hY2hlcyB0ZXh0IGFuYWx5c2lzIG1lYXN1cmVtZW50LiAKCldlIHdpbGwgYmUgd29ya2luZyB3aXRoIFUuUy4gSG91c2UgZmxvb3Igc3BlZWNoZXMgc3Bhbm5pbmcgSnVuZSAxOTk4IHRocm91Z2ggSnVseSAxOTk5LiBNb3JlIHNwZWNpZmljYWxseSwgd2Ugd2lsbDoKCigxKSBpbnZlc3RpZ2F0ZSB2YXJpYXRpb25zIGluIHNlbnRpbWVudCBhcyBhIGZ1bmN0aW9uIG9mIHBvbGl0aWNhbCBwYXJ0eQooMikgZXhwbG9yZSBzZW50aW1lbnQgZHluYW1pY3MgaW4gdGhlIGNvbnRleHQgb2YgUHJlc2lkZW50IEJpbGwgQ2xpbnRvbidzIGltcGVhY2htZW50LiAKCgojIEVudmlyb25tZW50IFByZXBhcmF0aW9uCmBgYHtyLCBlY2hvPUYsIG1lc3NhZ2U9Riwgd2FybmluZz1GfQojIERlZmluZSBjaHVuayBvcHRpb25zCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvPVQsIG1lc3NhZ2U9Riwgd2FybmluZz1GKQpgYGAKCmBgYHtyLCBtZXNzYWdlPUYsICBlY2hvPVR9CiMgTG9hZCBwYWNrYWdlcyAKbGlicmFyeShwYWNtYW4pCnBfbG9hZChyZWFkciwgZHBseXIsIHRpZHlyLCBnZ3Bsb3QyLCBqdG9vbHMsIAogICAgICAga25pdHIsIHJlc2hhcGUyLCBqc29ubGl0ZSwgbHVicmlkYXRlKQoKYGBgCgojIERhdGEgUHJlcGFyYXRpb24gCgpJbiB0aGUgcHJlcHJvY2Vzc2luZyBtb2R1bGUgKGAuLi9ub3RlYm9va3MvdGR0YV9wcmVwcm9jZXNzaW5nLlJtZGApLCB3ZSBzYXZlZCBhIHRpZHkgZm9ybWF0IGRhdGEuZnJhbWUgY29udGFpbmluZyBvdXIgY29ycHVzLiBXZSdsbCB1c2UgdGhhdCBhcyBhIHN0YXJ0aW5nIHBvaW50IGZvciBvdXIgd29yayBpbiB0aGlzIG1vZHVsZS4KCmBgYHtyfQoKZGF0X3RpZHkgPC0gcmVhZFJEUygnLi4vZGF0YS90ZHRhX2NsZWFuX2hvdXNlX2RhdGFfdGlkeS5SRFMnKQoKZGF0X3RpZHkKCmBgYAoKCkluIHRoaXMgZGF0YS5mcmFtZSwgd29yZHMgYXJlIHN0b3JlZCBpbiB0aGUgY2VsbHMgb2YgdGhlIGNvbHVtbiBgd29yZGAuIEFjY29yZGluZ2x5LCBhbiBlbnRpcmUgZG9jdW1lbnQsIGlkZW50aWZpZWQgYnkgdGhlIHVuaXF1ZSBkb2N1bWVudCBpZGVudGlmaWVyIGBkb2NfbnVtYCwgaXMgc3RvcmVkIGFjcm9zcyBtdWx0aXBsZSByb3dzLiBUaGlzIGRhdGEuZnJhbWUgYWxzbyBjb250YWlucyBtZXRhZGF0YSBsaWtlIHRoZSBuYW1lIG9mIHRoZSBzcGVha2VyIGFzc29jaWF0ZWQgd2l0aCBhIGdpdmVuIGRvY3VtZW50LCBhcyB3ZWxsIGFzIHRoZSB0aGUgc3BlYWtlcidzIHBhcnR5LCBkaXN0cmljdCwgYW5kIFN0YXRlLgoKCiMjIFRyYWluL1Rlc3QgU3BsaXQgCgpJZiB5b3UgaGF2ZSBlbm91Z2ggZGF0YSwgdGhlcmUncyByZWFsbHkgX25ldmVyXyBhbnkgcmVhc29uIHRvIG5vdCBzZXBhcmF0ZSB5b3VyIGRhdGEgaW50byBhIHRyYWluaW5nIHNldCBhbmQgdGVzdGluZyBzZXQuIE9mIGNvdXJzZSwgZGV0ZXJtaW5nIGhvdyBtdWNoIGRhdGEgaXMgKmVub3VnaCogY2FuIGJlIHRyaWNreSwgYmVjYXVzZSB5b3UgZG9uJ3Qgd2FudCB0byBjcmVhdGUgYSBzaXR1YXRpb24gd2hlcmUgeW91IGFyZSB1bmRlcnBvd2VyZWQgb3IgdW5hYmxlIHRvIGVzdGltYXRlIGEgdGFyZ2V0IHBhcmFtZXRlciB3aXRoIHN1ZmZpY2llbnQgcHJlY2lzaW9uLiAKCkhvd2V2ZXIsIHRyZWF0aW5nIGRvY3VtZW50cyBhcyBvdXIgdW5pdHMgb2Ygb2JzZXJ2YXRpb24sIHdlIGhhdmUgYW4gJE4gPSAkIDM1LDk1OSwgd2hpY2ggaXMgY2VydGFpbmx5IGxhcmdlIGVub3VnaCBmb3Igc3BsaXR0aW5nLgoKYGBge3J9Cgpkb2NfaWRzIDwtIHVuaXF1ZShkYXRfdGlkeSRkb2NfbnVtKSAjIEdldCBkb2N1bWVudCBJRHMKCm5fZG9jcyA9IC41MCAqIGxlbmd0aChkb2NfaWRzKSAjIENhbGN1bGF0ZSBudW1iZXIgb2YgZG9jdW1lbnRzIHRvIHNhbXBsZQoKc2V0LnNlZWQoNTQzNSkgIyBzZXQgc2VlZCBmb3IgcmVwcm9kdWNpYmlsaXR5Cgpkb2NfaWRzX3Rlc3QgPSBzYW1wbGUoZG9jX2lkcywgbl9kb2NzKSAjIHNhbXBsZSBkb2N1bWVudCBJRHMgZm9yIHRlc3QgZGF0YQoKZGF0X3RpZHkudHJhaW4gPC0gZGF0X3RpZHkgJT4lCiAgZmlsdGVyKGRvY19udW0gJSFpbiUgZG9jX2lkc190ZXN0KSAjIFNlbGVjdCBkb2N1bWVudHMgZm9yIHRyYWluaW5nCgpkYXRfdGlkeS50ZXN0IDwtIGRhdF90aWR5ICU+JQogIGZpbHRlcihkb2NfbnVtICVpbiUgZG9jX2lkc190ZXN0KSAjIFNlbGVjdCBkb2N1bWVudHMgZm9yIHRlc3QKCgpgYGAKCgoKIyMgQ29uZHVjdGluZyBXb3JkIGNvdW50cwoKIyMjIEludHJvZHVjdGlvbiB0byBkaWN0aW9uYXJpZXMKClRvIGNvbmR1Y3Qgd29yZCBjb3VudCBhbmFseXNlcywgeW91IG5lZWQgYSAqZGljdGlvbmFyeSogb3IgKmxleGljb24qIHRoYXQgc3BlY2lmaWVzIHRoZSB3b3JkcyBhc3NvY2lhdGVkIHdpdGggeW91ciB0YXJnZXQgY29uc3RydWN0KHMpLiAKClRvIHN0YXJ0LCB3ZSdsbCB3b3JrIHdpdGggdGhlIE5SQyBzZW50aW1lbnQgZGljdGlvbmFyeSwgd2hpY2ggaXMgb25lIG9mIHRocmVlIHNlbnRpbWVudCBkaWN0aW9uYXJpZXMgcGFja2FnZWQgd2l0aCBgdGlkeXRleHRgOiAKCjEuIEFGSU5OCjIuIGJpbmcKMy4gbnJjCgpUbyBhY2Nlc3MgdGhlIE5SQyBkaWN0aW9uYXJ5LCB3ZSdsbCB1c2UgdGhlIGBnZXRfc2VudGltZW50c2AgZnVuY3Rpb24gdG8gc3RvcmUgdGhlIE5SQyBkaWN0aW9uYXJ5IGluIGFuIG9iamVjdCBjYWxsZWQgYG5yY19zZW50YC4KCmBgYHtyfQpucmNfc2VudCA8LSBnZXRfc2VudGltZW50cygnbnJjJykKbnJjX3NlbnQKYGBgCgpgbnJjX3NlbnRgIGNvbnRhaW5zIHR3byBjb2x1bW5zLCBgd29yZGAsIHdoaWNoIGNvbnRhaW5zIHRoZSB3b3JkcyBpbiB0aGUgZGljdGlvbmFyeSwgYW5kIGBzZW50aW1lbnRgLCB3aGljaCBzcGVjaWZpZXMgdGhlIHNlbnRpbWVudCBsYWJlbCBhc3NvY2lhdGVkIHdpdGggdGhlIHdvcmQuIAoKYGBge3J9Cm5yY19zZW50ICU+JQogIGNvdW50KHNlbnRpbWVudCkKYGBgCgpUaGUgTlJDIGRpY3Rpb25hcnkgY29udGFpbnMgMTAgc2VudGltZW50IGNhdGVnb3JpZXMgYW5kIGVhY2ggb2YgdGhlc2UgY2F0ZWdvcmllcyBoYXZlIHZhcnlpbmcgbnVtYmVycyBvZiB3b3JkcyBhc3NvY2lhdGVkIHdpdGggdGhlbS4gCgpMZXQncyB0YWtlIGEgZ2xhbmNlIGF0IGZpcnN0IHdvcmRzIGluIGVhY2ggY2F0ZWdvcnkgYnkgc3ByZWFkaW5nIG91ciB0aWR5IGRhdGEuZnJhbWU6CgpgYGB7cn0KbnJjX3NlbnQgJT4lCiAgZ3JvdXBfYnkoc2VudGltZW50KSAlPiUgCiAgbXV0YXRlKHRlbXBfaWQgPSByb3dfbnVtYmVyKCkpICU+JSAjIENyZWF0ZSBhIHRlbXBvcmFyeSBJRCB0byB3ZWlnaHQgdG9wX24gYnkKICB0b3BfbihuID0gLTUwLCB3dD10ZW1wX2lkKSAlPiUgICAgICMgR2V0IGZpcnN0IDUwIGl0ZW1zIGluIGVhY2ggZ3JvdXAgCiAgbXV0YXRlKHRlbXBfaWQgPSByb3dfbnVtYmVyKCkpICU+JSAjIENyZWF0ZSBhIHRlbXBvcmFyeSB1bmlxdWUgSUQgZm9yIGVhY2ggd29yZCBpbiBlYWNoIGdyb3VwCiAgdW5ncm91cCgpICU+JQogIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSBzZW50aW1lbnQsIHZhbHVlc19mcm9tID0gd29yZCkgJT4lICMgU3ByZWFkIG91ciBkYXRhCiAgc2VsZWN0KC10ZW1wX2lkKQpgYGAKCgpHbGFuY2luZyBhdCB0aGVzZSB3b3JkcywgaXQncyBjbGVhciB0aGF0IHdvcmRzIGFyZSByZXBlYXRlZCBpbiBzb21lIGNhdGVnb3JpZXMuIAoKCjxicj4KCjxkaXYgY2xhc3M9ImFsZXJ0IGFsZXJ0LXN1Y2Nlc3MiIHJvbGU9ImFsZXJ0Ij4KICA8c3Ryb25nPlF1ZXN0aW9uOjwvc3Ryb25nPiBXaGF0IG90aGVyIGNoYXJhY3RlcmlzdGljcyBzdGFuZCBvdXQsIGlmIGFueT8KPC9kaXY+CgojIyMgQSBjYXV0aW9uYXJ5IG5vdGUgb24gd29yZCBjb3VudCB2YWxpZGl0eQoKT25lIG9mIHRoZSBncmVhdGVzdCBzdHJlbmd0aHMgb2YgZGljdGlvbmFyeS1iYXNlZCB0ZXh0IG1lYXN1cmVtZW50IG1ldGhvZHMgaXMgdGhhdCB0aGV5IGFsbG93IHlvdSB0byBwcmVjaXNlbHkgZGVmaW5lIHRoZSBjb25zdHJ1Y3QgeW91IGFyZSBpbnRlcmVzdGVkIGluLiBUaGlzIHdvcmtzIGV4dHJlbWVseSB3ZWxsIHdoZW4geW91IGFyZSBpbnRlcmVzdGVkIGluIHNwZWNpZmljIHdvcmRzIG9yIHR5cGVzIG9mIHdvcmRzLiAKCkZvciBleGFtcGxlLCBpZiB5b3UgYXJlIGludGVyZXN0ZWQgaW4gKmZ1bmN0aW9uIHdvcmRzKiwgdGhlbiBpdCB3b3VsZCBuZXZlciBtYWtlIHNlbnNlIHRvIHVzZSBhbnl0aGluZyBvdGhlciB0aGFuIGEgZGljdGlvbmFyeS1iYXNlZCBhcHByb2FjaC4gU2ltaWxhcmx5LCBpZiB5b3UgYXJlIHRydWx5IGludGVyZXN0ZWQgaW4gdGhlIHVzYWdlIG9mIHNwZWNpZmljICpwb3NpdGl2ZSogb3IgKm5lZ2F0aXZlKiB3b3JkcywgdGhlbiwgYWdhaW4sIGl0IHByb2JhYmx5IHdvdWxkbid0IG1ha2Ugc2Vuc2UgdG8gdXNlIGFueXRoaW5nIG90aGVyIHRoYW4gYSBkaWN0aW9uYXJ5IGFwcHJvYWNoLiAKCkluIHRoZXNlIGV4YW1wbGVzLCB0aGVyZSBpcyBhIDE6MSByZWxhdGlvbnNoaXAgYmV0d2VlbiB0aGUgdGFyZ2V0IGNvbnN0cnVjdCBhbmQgdGhlIG9wZXJhdGlvbmFsaXphdGlvbi4gSG93ZXZlciwgdGhpcyAxOjEgcmVsYXRpb25zaGlwIGlzIGRpZmZpY3VsdCB0byBtYWludGFpbiBmb3IgbW9yZSBhYnN0cmFjdCBjb25zdHJ1Y3RzLCBsaWtlICJwb3NpdGl2ZSBzZW50aW1lbnQiIG9yICJuZWdhdGl2ZSBzZW50aW1lbnQiLiBJbiBzdWNoIGNhc2VzLCB5b3UgKG9yIHNvbWVvbmUgZWxzZSkgaGFzIHRvIGRlY2lkZSB3aGljaCB3b3JkcyBldm9rZSAicG9zaXRpdmUgc2VudGltZW50IiBvciAibmVnYXRpdmUgc2VudGltZW50Ii4gCgpGdXJ0aGVyLCB3ZSBhcmUgb2Z0ZW4gaW50ZXJlc3RlZCBpbiBleHByZXNzaW9ucyBvZiBtZWFuaW5nIHRoYXQgbWF5IG9wZXJhdGUgYWJvdmUgdGhlIHdvcmQgbGV2ZWwuIEZvciBpbnN0YW5jZSwgY29uc2lkZXIgdGhlIGZvbGxvd2luZyBleGFtcGxlOiAKCmBMZXQncyBqdXN0IHNheS4uLkkgZGlkbid0IGxvdmUgaXQnCgpNb3N0IGRpY3Rpb25hcnktYmFzZWQgd29yZCBjb3VudCBtZXRob2RzIHdvdWxkIGVzdGltYXRlIHRoZSBzZW50aW1lbnQgZXhwcmVzc2VkIGluIHRoaXMgc2VudGVuY2UgYXMgInBvc2l0aXZlIiBiZWNhdXNlIG9mIHRoZSB0b2tlbiBgbG92ZWAuIEhvd2V2ZXIsIGNvbnNpZGVyaW5nIHRoZSBlbnRpcmUgKmNvbnRleHQqIG9mIHRoaXMgZXhhbXBsZSwgd2UgY2FuIGluZmVyIHRoYXQgdGhlIG1vc3QgbGlrZWx5IHNlbnRpbWVudCBpcyBwcm9iYWJseSAibmVnYXRpdmUiLiAKCkluIHN1bSwgZGljdGlvbmFyeS1iYXNlZCB3b3JkIGNvdW50IGFwcHJvYWNoZXMgY2FuIGJlIHF1aXRlIHBvd2VyZnVsOyBob3dldmVyLCB0aGV5IGhhdmUgdHdvIG5vdGFibGUgc2hvcnRjb21pbmdzOiAKCjEuIERlcGVuZGVuY2Ugb24gZGljdGlvbmFyeSB2YWxpZGl0eQoyLiBDYW5ub3QgYWNjb3VudCBmb3IgY29udGV4dAoKVGhpcyAqZG9lcyBub3QqIG1lYW4gdGhhdCB5b3Ugc2hvdWxkbid0IHVzZSBkaWN0aW9uYXJ5LWJhc2VkIHdvcmQgY291bnQgbWV0aG9kcy4gSG93ZXZlciwgaXQgKmRvZXMqIG1lYW4gdGhhdCB5b3Ugc2hvdWxkIGtlZXAgdGhlc2Ugc2hvcnQgY29taW5ncyBpbiBtaW5kLiBBbmQsIGV2ZW4gYmV0dGVyLCB5b3Ugc2hvdWxkIHRyeSB0byBhY2NvdW50IGZvciB0aGVtIHRocm91Z2ggdmFsaWRhdGlvbi4gCgojIyMgYHRpZHl0ZXh0YCB3b3JkIGNvdW50IHNlbnRpbWVudCBhbmFseXNpcwoKSW4gcHJpbmNpcGxlLCBgdGlkeXRleHRgIG1ha2VzIGRpY3Rpb25hcnktYmFzZWQgd29yZCBjb3VudCBzZW50aW1lbnQgYW5hbHlzaXMgcXVpdGUgc2ltcGxlLiAKClRvIGNvdW50IHRoZSB3b3JkcyB3ZSBjYW4ganVzdDogCgoxLiBDb25kdWN0IGFuIGBpbm5lcl9qb2luYCBiZXR3ZWVuIG91ciBkYXRhIGFuZCBvdXIgc2VudGltZW50IGRpY3Rpb25hcnkKMi4gQ291bnQgdGhlIG1hdGNoZXMKCldlJ2xsIGFsc28gZGl2aWRlIHRoZSBudW1iZXIgb2YgbWF0Y2hlcyBmb3IgZWFjaCBzZW50aW1lbnQgZG9tYWluIGJ5IHRoZSB0b3RhbCBudW1iZXIgb2Ygd29yZHMgaW4gb3VyIGNvcnB1cy4gVGhpcyB3aWxsIHRlbGwgdXMgdGhlIHByb3BvcnRpb24gYXNzb2NpYXRlZCB3aXRoIGVhY2ggc2VudGltZW50IGRvbWFpbi4KCmBgYHtyfQoKdG90YWxfd29yZHMgPSBucm93KGRhdF90aWR5LnRyYWluKQoKZGF0X3RpZHkudHJhaW4gJT4lCiAgaW5uZXJfam9pbihucmNfc2VudCkgJT4lCiAgY291bnQoc2VudGltZW50KSAlPiUKICBtdXRhdGUocHJvcCA9IG4vdG90YWxfd29yZHMpICU+JQogIGFycmFuZ2UoZGVzYyhwcm9wKSkKCmBgYAoKCiMjIyBBZmZlY3RpdmUgc2VudGltZW50IGJ5IFBvbGl0aWNhbCBQYXJ0eQoKV2UgY2FuIGFsc28gc3Vic2V0IG91ciBkYXRhIGluIG9yZGVyIHRvIGFzayBtb3JlIHNwZWNpZmljIHF1ZXN0aW9ucy4gRm9yIGluc3RhbmNlLCB3ZSBjYW4gZWFzaWx5IGVzdGltYXRlIHNlbnRpbWVudCBwcm9wb3J0aW9ucyBmb3IgRGVtb2NyYXRzIGFuZCBSZXB1YmxpY2Fucy4KCmBgYHtyfQoKZGF0X3RpZHkudHJhaW4uc2VudCA8LSBkYXRfdGlkeS50cmFpbiAlPiUKICBncm91cF9ieShQYXJ0eSkgJT4lICMgR3JvdXAgYnkgUGFydHkKICBtdXRhdGUodG90YWxfd29yZHMgPSBuKCkpICU+JSAjIENhbGN1bGF0ZSB0aGUgdG90YWwgd29yZHMgaW4gZWFjaCBncm91cAogIHVuZ3JvdXAoKSAlPiUgCiAgaW5uZXJfam9pbihucmNfc2VudCkgIyBEcm9wIHdvcmRzIHRoYXQgYXJlbid0IGluIHNlbnRpbWVudCBkaWN0aW9uYXJ5CiAgCmRhdF90aWR5LnRyYWluLnNlbnQgJT4lIGNvdW50KHRvdGFsX3dvcmRzLCBQYXJ0eSwgc2VudGltZW50KSAlPiUgIyBDb3VudCB0aGUgbnVtYmVyIG9mIHJvd3MgaW4gZWFjaCBQYXJ0eSBmb3IgZWFjaCBzZW50aW1lbnQKICBtdXRhdGUocHJvcCA9IG4vdG90YWxfd29yZHMpICU+JSAjIENhbGN1bGF0ZSB0aGUgcHJvcG9ydGlvbgogIGFycmFuZ2UoZGVzYyhwcm9wKSkgJT4lICMgQXJyYW5nZSBpbiBkZXNjZW5kaW5nIG9yZGVyIGJ5IHByb3BvcnRpb24gcG9zaXRpdmUKICBzZWxlY3QoLW4sIC10b3RhbF93b3JkcykgJT4lCiAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbT0nUGFydHknLCB2YWx1ZXNfZnJvbSA9ICdwcm9wJykKCgoKYGBgCgoKT3ZlcmFsbCwgaXQgbG9va3MgbGlrZSB0aGVyZSBpcyB2ZXJ5IGxpdHRsZSBtZWFuIHNlbnRpbWVudCB2YXJpYXRpb24gYmV0d2VlbiBSZXB1YmxpY2FucyBhbmQgRGVtb2NyYXRzLiBIb3dldmVyLCB3ZSd2ZSBjb2xsYXBzZWQgYWNyb3NzIGRvY3VtZW50cy4gVG8gZ2V0IGEgYmV0dGVyIGlkZWEgb2YgaG93IGV4cHJlc3Npb25zIG9mIGFmZmVjdGl2ZSBzZW50aW1lbnQgdmFyeSBhY3Jvc3MgUGFydGllcywgbGV0J3MgdmlzdWFsaXplIHRoZSBkaXN0cmlidXRpb24gb2Ygc2VudGltZW50IGluIGRvY3VtZW50cyAKCgoKYGBge3J9CiAKZGF0X3RpZHkudHJhaW4uc2VudCAlPiUKICBmaWx0ZXIoUGFydHkgIT0nSW5kZXBlbmRlbnQnKSAlPiUKICBjb3VudCh0b3RhbF93b3JkcywgUGFydHksIGRvY19udW0sIHNlbnRpbWVudCkgJT4lCiAgbXV0YXRlKHByb3AgPSBuL3RvdGFsX3dvcmRzKSAlPiUKICBnZ3Bsb3QoYWVzKHkgPSBwcm9wLCB4ID0gUGFydHksIGNvbG9yPVBhcnR5KSkgKyAKICBnZW9tX2ppdHRlcihhbHBoYT0uMjUpICsgCiAgZmFjZXRfd3JhcCgufnNlbnRpbWVudCwgbmNvbD01KSArIAogIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzID0gYygiYmx1ZSIsICJyZWQiKSkgKwogIHRoZW1lX2FwYSgpICsgCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkgKyAKICBnZ3RpdGxlKCdEb2N1bWVudC1sZXZlbCBwcm9wb3J0aW9ucyBvZiBzZW50aW1lbnQgd29yZHMgYnkgcGFydHknKSArIAogIHhsYWIoJ1BhcnR5JykgKyAKICB5bGFiKCdQcm9wb3J0aW9uJykKCmBgYAoKCgpXaGF0IGlmIHdlIGxvb2sgYXQgdGhlIGRvY3VtZW50ICpOKnMgb2Ygc2VudGltZW50IHdvcmRzIGluc3RlYWQgb2YgcHJvcG9ydGlvbnM/CgpgYGB7cn0KIApkYXRfdGlkeS50cmFpbi5zZW50ICU+JQogIGZpbHRlcihQYXJ0eSAhPSdJbmRlcGVuZGVudCcpICU+JQogIGNvdW50KHRvdGFsX3dvcmRzLCBQYXJ0eSwgZG9jX251bSwgc2VudGltZW50KSAlPiUKICBtdXRhdGUocHJvcCA9IG4vdG90YWxfd29yZHMpICU+JQogIGdncGxvdChhZXMoeSA9IG4sIHggPSBQYXJ0eSwgY29sb3I9UGFydHkpKSArIAogIGdlb21faml0dGVyKGFscGhhPS4yNSkgKyAKICBmYWNldF93cmFwKC5+c2VudGltZW50LCBuY29sPTUpICsgCiAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXMgPSBjKCJibHVlIiwgInJlZCIpKSArCiAgdGhlbWUoKSArIAogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpICsgCiAgZ2d0aXRsZSgnRG9jdW1lbnQtbGV2ZWwgTnMgb2Ygc2VudGltZW50IHdvcmRzIGJ5IHBhcnR5JykgKyAKICB4bGFiKCdQYXJ0eScpICsgCiAgeWxhYignTicpCgpgYGAKCgoKPGJyPgoKPGRpdiBjbGFzcz0iYWxlcnQgYWxlcnQtc3VjY2VzcyIgcm9sZT0iYWxlcnQiPgogIDxzdHJvbmc+UXVlc3Rpb246PC9zdHJvbmc+IFdoeSBtaWdodCB3ZSB3YW50IHRvIGxvb2sgYXQgcHJvcG9ydGlvbiB2cy4gTiAob3IgdmljZSB2ZXJzYSk/CjwvZGl2PgoKCiMjIyBEeW5hbWljcyBpbiBhZmZlY3RpdmUgc2VudGltZW50IGJ5IHBvbGl0aWNhbCBwYXJ0eQoKQ2xlYXJseSwgdGhlcmUgaXNuJ3QgbXVjaCBtYXJnaW5hbCBiZXR3ZWVuIGdyb3VwIHZhcmlhdGlvbiBpbiBhZmZlY3RpdmUgc2VudGltZW50IGluIHRoaXMgZGF0YXNldC4gSG93ZXZlciwgbWF5YmUgdGhlcmUgYXJlIGludGVyZXN0aW5nIGVmZmVjdHMgb3BlcmF0aW5nIGF0IG90aGVyIGxldmVscyEKCkxldCdzIGV4YW1pbmUgdGVtcG9yYWwgdmFyaWF0aW9uIGluIGFmZmVjdGl2ZSBzZW50aW1lbnQgYmV0d2VlbiBwYXJ0aWVzLiBUbyBkbyB0aGlzLCB3ZSB3aWxsIHRha2UgYSBzaW1pbGFyIGFwcHJvYWNoLCBidXQgaW5zdGVhZCBvZiBjb3VudGluZyB0aGUgc2VudGltZW50IGluIGVhY2ggZG9jdW1lbnQsIHdlJ2xsIGNvdW50IHRoZSBzZW50aW1lbnQgb24gZWFjaCBvYnNlcnZlZCBkYXkuIAoKRmlyc3QsIGhvd2V2ZXIsIGxldCdzIGdsYW5jZSBhdCB0aGUgZGlzdHJpYnV0aW9uIG9mIGRvY3VtZW50cyBhY3Jvc3MgdGltZS4gRm9yIHJlZmVyZW5jZSwgbGV0J3MgYWxzbyBhZGQgdmVydGljYWwgbGluZXMgdG8gaW5kaWNhdGUgdGhlIGRhdGVzIG9uIHdoaWNoIENsaW50b24ncyBpbXBlYWNobWVudCB3YXMgaW5pYXRlZCBhbmQgdm90ZWQgb24uCgoKCmBgYHtyLCBmaWcud2lkdGg9MTB9CgpzZW50X3RpbWUgPC0gZGF0X3RpZHkudHJhaW4uc2VudCAlPiUKICBmaWx0ZXIoUGFydHkgIT0nSW5kZXBlbmRlbnQnKSAlPiUKICBkaXN0aW5jdChkb2NfbnVtLCAua2VlcF9hbGwgPSBUKSAlPiUKICBjb3VudChkYXRlLCBQYXJ0eSkgJT4lCiAgZ2dwbG90KGFlcyh5ID0gbiwgeCA9IGRhdGUsIGNvbG9yPVBhcnR5KSkgKyAKICBnZW9tX2xpbmUoYWxwaGE9LjUpICsgCiAgZmFjZXRfd3JhcChQYXJ0eSB+LiAsIG5jb2w9MSkgKwogIHRoZW1lX2FwYSgpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQ9YXMubnVtZXJpYyhhc19kYXRldGltZSgnMTk5OC0xMC0wOCcpKSwgbGluZXR5cGU9MiwgYWxwaGE9LjI1KSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0PWFzLm51bWVyaWMoYXNfZGF0ZXRpbWUoJzE5OTgtMTItMTknKSksIGxpbmV0eXBlPTIsIGFscGhhPS4yNSkgKwogIGdlb21fcG9pbnQoKSArCiAgdGhlbWVfYXBhKCkgKwogIGdndGl0bGUoJ04gb2YgZG9jdW1lbnRzIGFjcm9zcyB0aW1lIGJ5IHBhcnR5JykgKwogIHlsYWIoJ04nKSArIAogIHhsYWIoJ0RhdGUnKQoKZ2dwbG90bHkoc2VudF90aW1lKQoKCmBgYAoKQ2xlYXJseSwgdGhlcmUgaXMgc3Vic3RhbnRpYWwgdmFyaWF0aW9uIGluIHRoZSBudW1iZXIgb2YgZG9jdW1lbnRzIChpLmUuIHNwZWVjaGVzIGdpdmVuIGJ5IGluZGl2aWR1YWwgc3BlYWtlcnMpIGFjcm9zcyB0aW1lLiAKCgo8YnI+Cgo8ZGl2IGNsYXNzPSJhbGVydCBhbGVydC1zdWNjZXNzIiByb2xlPSJhbGVydCI+CiAgPHN0cm9uZz5RdWVzdGlvbjo8L3N0cm9uZz4gV2hhdCBhcmUgb3VyIHNhbXBsZSBzaXplcyBvbiB0aGUgaW1wZWFjaG1lbnQtcmVsZXZhbnQgZGF5cz8KPC9kaXY+CgoKCk5vdywgbGV0J3MgcGxvdCBzZW50aW1lbnQgYWNyb3NzIHRpbWUgYnkgcGFydHkuIAoKYGBge3IsIGZpZy5oZWlnaHQ9MTAsIGZpZy53aWR0aD0xMH0KCmRhdF90aWR5LnRyYWluLnNlbnQgJT4lCiAgZmlsdGVyKFBhcnR5ICE9J0luZGVwZW5kZW50JykgJT4lCiAgY291bnQodG90YWxfd29yZHMsIFBhcnR5LCBkYXRlLCBzZW50aW1lbnQpICU+JQogIG11dGF0ZShwcm9wID0gbi90b3RhbF93b3JkcykgJT4lCiAgZ2dwbG90KGFlcyh5ID0gbiwgeCA9IGRhdGUsIGNvbG9yPVBhcnR5KSkgKyAKICBmYWNldF93cmFwKHNlbnRpbWVudH5QYXJ0eSwgbmNvbD00KSArIAogIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzID0gYygiYmx1ZSIsICJyZWQiKSkgKwogIHRoZW1lKCkgKyAKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKSArIAogIGdndGl0bGUoJ0RvY3VtZW50LWxldmVsIE5zIG9mIHNlbnRpbWVudCB3b3JkcyBieSBwYXJ0eScpICsgCiAgeGxhYignUGFydHknKSArIAogIHlsYWIoJ04nKSArIAogIGdlb21fc21vb3RoKGNvbG9yPSdibGFjaycpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQ9YXMubnVtZXJpYyhhc19kYXRldGltZSgnMTk5OC0xMC0wOCcpKSwgbGluZXR5cGU9MikgKwogIGdlb21fdmxpbmUoeGludGVyY2VwdD1hcy5udW1lcmljKGFzX2RhdGV0aW1lKCcxOTk4LTEyLTE5JykpLCBsaW5ldHlwZT0yKSArCiAgZ2VvbV9wb2ludChhbHBoYT0uMjUpIAoKYGBgCgoKCmBgYHtyLCBmaWcuaGVpZ2h0PTEwLCBmaWcud2lkdGg9MTB9CgpkYXRfdGlkeS50cmFpbi5zZW50ICU+JQogIGZpbHRlcihQYXJ0eSAhPSdJbmRlcGVuZGVudCcpICU+JQogIGNvdW50KHRvdGFsX3dvcmRzLCBQYXJ0eSwgZGF0ZSwgc2VudGltZW50KSAlPiUKICBtdXRhdGUocHJvcCA9IG4vdG90YWxfd29yZHMpICU+JQogIGdncGxvdChhZXMoeSA9IHByb3AsIHggPSBkYXRlLCBjb2xvcj1QYXJ0eSkpICsgCiAgZmFjZXRfd3JhcChzZW50aW1lbnR+UGFydHksIG5jb2w9NCkgKyAKICBzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcyA9IGMoImJsdWUiLCAicmVkIikpICsKICB0aGVtZSgpICsgCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkgKyAKICBnZ3RpdGxlKCdEb2N1bWVudC1sZXZlbCBwcm9wb3J0aW9ucyBvZiBzZW50aW1lbnQgd29yZHMgYnkgcGFydHknKSArIAogIHhsYWIoJ1BhcnR5JykgKyAKICB5bGFiKCdOJykgKyAKICBnZW9tX3Ntb290aChjb2xvcj0nYmxhY2snKSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0PWFzLm51bWVyaWMoYXNfZGF0ZXRpbWUoJzE5OTgtMTAtMDgnKSksIGxpbmV0eXBlPTIpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQ9YXMubnVtZXJpYyhhc19kYXRldGltZSgnMTk5OC0xMi0xOScpKSwgbGluZXR5cGU9MikgKwogIGdlb21fcG9pbnQoYWxwaGE9LjI1KSAKCmBgYAoKCjxicj4KCjxkaXYgY2xhc3M9ImFsZXJ0IGFsZXJ0LXN1Y2Nlc3MiIHJvbGU9ImFsZXJ0Ij4KICA8c3Ryb25nPlF1ZXN0aW9uOjwvc3Ryb25nPiBXaGF0IGNhbiB3ZSBsZWFybiBmcm9tIHRoaXMgZmlndXJlPwo8L2Rpdj4KCiMjIEh5cG90aGVzaXMgdGVzdGluZyB3aXRoIHdvcmQgY291bnRzCgpgYGB7cn0KCmRhdF90aWR5LnRyYWluLnNwZWFrZXIgPC0gZGF0X3RpZHkudHJhaW4gJT4lCiAgZ3JvdXBfYnkoUGFydHksIHNwZWFrZXIpICU+JQogIG11dGF0ZShzcGVha2VyX3RvdGFsX3dvcmRzID0gbigpKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgaW5uZXJfam9pbihucmNfc2VudCkgJT4lCiAgY291bnQoUGFydHksIHNwZWFrZXIsIHNwZWFrZXJfdG90YWxfd29yZHMsIGRhdGUsIHNlbnRpbWVudCkgJT4lCiAgZ3JvdXBfYnkoc3BlYWtlciwgc2VudGltZW50KSAlPiUKICBtdXRhdGUoc3BlYWtlcl9zZW50X21lYW5zID0gbWVhbihuKSwgCiAgICAgICAgIHNwZWFrZXJfc2VudF9jbnRyID0gbiAtIHNwZWFrZXJfc2VudF9tZWFucykKICAKYGBgCgoKCmBgYHtyfQoKZGF0X3RpZHkudHJhaW4uc3BlYWtlciA8LSBkYXRfdGlkeS50cmFpbiAlPiUKICBmaWx0ZXIoUGFydHkgIT0gJ0luZGVwZW5kZW50JykgJT4lCiAgZ3JvdXBfYnkoUGFydHksIHNwZWFrZXIsIGRhdGUpICU+JQogIG11dGF0ZShzcGVha2VyX2RheV9uID0gbigpKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgaW5uZXJfam9pbihucmNfc2VudCkgJT4lCiAgY291bnQoUGFydHksIHNwZWFrZXIsIGRhdGUsIHNwZWFrZXJfZGF5X24sIHNlbnRpbWVudCkgJT4lCiAgbXV0YXRlKHNwZWFrZXJfZGF5X3NlbnRfcHJvcCA9IG4vc3BlYWtlcl9kYXlfbikKCgoKCiMgQ3JlYXRlIGEgZGF0ZSByYW5nZSBmcm9tIHRoZSBtaW4vbWF4IGRhdGVzIGluIG91ciB0cmFpbmluZyBkYXRhCmRhdGVfZ3JpZCA8LSB0aWJibGUoZGF0ZSA9IHNlcShtaW4oZGF0X3RpZHkudHJhaW4uc3BlYWtlciRkYXRlKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXgoZGF0X3RpZHkudHJhaW4uc3BlYWtlciRkYXRlKSwgYnk9J2RheXMnKSkgJT4lCiAgbXV0YXRlKGRhdGVfaW50ID0gcm93X251bWJlcigpLCAjIEFzc29jaWF0ZSBlYWNoIGRhdGUgd2l0aCBhbiBpbnRlZ2VyIAogICAgICAgICBkYXRlX2ludF9zY2FsZWQgPSBkYXRlX2ludC8xMDApCgpkYXRfdGlkeS50cmFpbi5zcGVha2VyIDwtIGRhdF90aWR5LnRyYWluLnNwZWFrZXIgJT4lCiAgbGVmdF9qb2luKGRhdGVfZ3JpZCkgJT4lCiAgbXV0YXRlKGRhdGVfaW50X3NjYWxlZCA9IGRhdGVfaW50LzEwMCwgCiAgICAgICAgIGltcGVhY2htZW50XzEgPSBpZmVsc2UoZGF0ZSA9PSBhc19kYXRldGltZSgnMTk5OC0xMC0wOCcpLCAxLCAwKSwKICAgICAgICAgaW1wZWFjaG1lbnRfMiA9IGlmZWxzZShkYXRlID09IGFzX2RhdGV0aW1lKCcxOTk4LTEyLTE5JyksIDEsIDApKQogIApgYGAKCgpgYGB7cn0KCnRyYWluLnNwZWFrZXIubmVnYXRpdmUubTEgPC0gZGF0X3RpZHkudHJhaW4uc3BlYWtlciAlPiUKICBmaWx0ZXIoc2VudGltZW50PT0nbmVnYXRpdmUnKSAlPiUKICBsbWVyKHNwZWFrZXJfZGF5X3NlbnRfcHJvcCB+IDEgKyBQYXJ0eSppbXBlYWNobWVudF8xICsgUGFydHkqaW1wZWFjaG1lbnRfMiAgKyAoMSAgfCBzcGVha2VyKSArICgxICsgUGFydHkgfCBkYXRlX2ludCksIGRhdGE9LikKCnN1bW1hcnkodHJhaW4uc3BlYWtlci5uZWdhdGl2ZS5tMSkKCmBgYAoKCgpgYGB7cn0KCgpkYXRfdGlkeS50cmFpbi5zcGVha2VyLnByZWRfZ3JpZCA8LSBleHBhbmQuZ3JpZChQYXJ0eSA9IHVuaXF1ZShkYXRfdGlkeS50cmFpbi5zcGVha2VyJFBhcnR5KSwgCiAgICAgICAgICAgIHNwZWFrZXIgPSAnbmV3X3NwZWFrZXInLCAKICAgICAgICAgICAgZGF0ZV9pbnQgPSB1bmlxdWUoZGF0X3RpZHkudHJhaW4uc3BlYWtlciRkYXRlX2ludCkpCgoKZGF0X3RpZHkudHJhaW4uc3BlYWtlci5wcmVkX2dyaWQgPC0gZGF0X3RpZHkudHJhaW4uc3BlYWtlci5wcmVkX2dyaWQgJT4lCiAgbGVmdF9qb2luKGRhdGVfZ3JpZCkgJT4lCiAgbXV0YXRlKGltcGVhY2htZW50XzEgPSBpZmVsc2UoZGF0ZSA9PSBhc19kYXRldGltZSgnMTk5OC0xMC0wOCcpLCAxLCAwKSwKICAgICAgICAgaW1wZWFjaG1lbnRfMiA9IGlmZWxzZShkYXRlID09IGFzX2RhdGV0aW1lKCcxOTk4LTEyLTE5JyksIDEsIDApKQoKCgp0cmFpbi5zcGVha2VyLm5lZ2F0aXZlLm0xLnByZWQgPC0gZGF0X3RpZHkudHJhaW4uc3BlYWtlci5wcmVkX2dyaWQgJT4lCiAgbXV0YXRlKHByZWRzID0gcHJlZGljdCh0cmFpbi5zcGVha2VyLm5lZ2F0aXZlLm0xLCBuZXdkYXRhPWRhdF90aWR5LnRyYWluLnNwZWFrZXIucHJlZF9ncmlkLCBhbGxvdy5uZXcubGV2ZWxzPVQpKQogIAoKCnRyYWluLnNwZWFrZXIubmVnYXRpdmUubTEucHJlZCAlPiUKICBsZWZ0X2pvaW4oZGF0ZV9ncmlkKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBkYXRlLCB5ID0gcHJlZHMsIGNvbG9yPVBhcnR5KSkgKyAKICBnZW9tX2xpbmUoKSArIAogIGdlb21fcG9pbnQoYWVzKHkgPSBwcmVkcyksIGFscGhhPS4yNSkgKwogIHRoZW1lX2FwYSgpICsgCiAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXMgPSBjKCJibHVlIiwgInJlZCIpKSArIAogIHRoZW1lX2FwYSgpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQ9YXMubnVtZXJpYyhhc19kYXRldGltZSgnMTk5OC0xMC0wOCcpKSwgbGluZXR5cGU9MiwgYWxwaGE9LjI1KSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0PWFzLm51bWVyaWMoYXNfZGF0ZXRpbWUoJzE5OTgtMTItMTknKSksIGxpbmV0eXBlPTIsIGFscGhhPS4yNSkgKwogIGdndGl0bGUoJ0RhaWx5IGV4cGVjdGVkIHByb3BvcnRpb24gb2YgbmVnYXRpdmUgbGFuZ3VhZ2UgZm9yIGFuIGF2ZXJhZ2Ugc3BlYWtlcicgKSArCiAgeWxhYignU3BlYWtlciBwcm9wb3J0aW9uIG5lZ2F0aXZlIGxhbmd1YWdlJykgKyAKICB4bGFiKCdEYXRlJykKCiAgCmBgYAoKCgojIyBEaXNndXN0IAoKYGBge3J9CgoKdHJhaW4uc3BlYWtlci5kaXNndXN0Lm0xIDwtIGRhdF90aWR5LnRyYWluLnNwZWFrZXIgJT4lCiAgZmlsdGVyKHNlbnRpbWVudD09J2Rpc2d1c3QnKSAlPiUKICBsbWVyKHNwZWFrZXJfZGF5X3NlbnRfcHJvcCB+IDEgKyBQYXJ0eSppbXBlYWNobWVudF8xICsgUGFydHkqaW1wZWFjaG1lbnRfMiAgKyAoMSAgfCBzcGVha2VyKSArICgxICsgUGFydHkgfCBkYXRlX2ludCksIGRhdGE9LikKCnN1bW1hcnkodHJhaW4uc3BlYWtlci5kaXNndXN0Lm0xKQoKCgp0cmFpbi5zcGVha2VyLmRpc2d1c3QubTEucHJlZCA8LSBkYXRfdGlkeS50cmFpbi5zcGVha2VyLnByZWRfZ3JpZCAlPiUKICBtdXRhdGUocHJlZHMgPSBwcmVkaWN0KHRyYWluLnNwZWFrZXIuZGlzZ3VzdC5tMSwgbmV3ZGF0YT1kYXRfdGlkeS50cmFpbi5zcGVha2VyLnByZWRfZ3JpZCwgYWxsb3cubmV3LmxldmVscz1UKSkKICAKCgp0cmFpbi5zcGVha2VyLmRpc2d1c3QubTEucHJlZCAlPiUKICBsZWZ0X2pvaW4oZGF0ZV9ncmlkKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBkYXRlLCB5ID0gcHJlZHMsIGNvbG9yPVBhcnR5KSkgKyAKICBnZW9tX2xpbmUoKSArIAogIGdlb21fcG9pbnQoYWVzKHkgPSBwcmVkcyksIGFscGhhPS4yNSkgKwogIHRoZW1lX2FwYSgpICsgCiAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXMgPSBjKCJibHVlIiwgInJlZCIpKSArIAogIHRoZW1lX2FwYSgpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQ9YXMubnVtZXJpYyhhc19kYXRldGltZSgnMTk5OC0xMC0wOCcpKSwgbGluZXR5cGU9MiwgYWxwaGE9LjI1KSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0PWFzLm51bWVyaWMoYXNfZGF0ZXRpbWUoJzE5OTgtMTItMTknKSksIGxpbmV0eXBlPTIsIGFscGhhPS4yNSkgKwogIGdndGl0bGUoJ0RhaWx5IGV4cGVjdGVkIHByb3BvcnRpb24gb2YgZGlzZ3VzdCBsYW5ndWFnZSBmb3IgYW4gYXZlcmFnZSBzcGVha2VyJyApICsKICB5bGFiKCdTcGVha2VyIHByb3BvcnRpb24gZGlzZ3VzdCBsYW5ndWFnZScpICsgCiAgeGxhYignRGF0ZScpCgoKYGBgCgoKCmBgYHtyfQoKCgp0cmFpbi5zcGVha2VyLmFsbC5tMSA8LSBkYXRfdGlkeS50cmFpbi5zcGVha2VyICU+JQogIGxtZXIoc3BlYWtlcl9kYXlfc2VudF9wcm9wIH4gMSArIFBhcnR5KmltcGVhY2htZW50XzEgKyBQYXJ0eSppbXBlYWNobWVudF8yICArICgxIHwgc3BlYWtlcikgKyAoMSArIFBhcnR5IHwgZGF0ZV9pbnQpICsgKDEgKyBpbXBlYWNobWVudF8xICArIGltcGVhY2htZW50XzIgfCBzZW50aW1lbnQpLCBkYXRhPS4pCgpzdW1tYXJ5KHRyYWluLnNwZWFrZXIuYWxsLm0xKQoKCmRhdF90aWR5LnRyYWluLnNwZWFrZXIucHJlZF9ncmlkLmFsbF9zZW50IDwtIGV4cGFuZC5ncmlkKFBhcnR5ID0gdW5pcXVlKGRhdF90aWR5LnRyYWluLnNwZWFrZXIkUGFydHkpLCAKICAgICAgICAgICAgc3BlYWtlciA9ICduZXdfc3BlYWtlcicsIAogICAgICAgICAgICBkYXRlX2ludCA9IHVuaXF1ZShkYXRfdGlkeS50cmFpbi5zcGVha2VyJGRhdGVfaW50KSwKICAgICAgICAgICAgc2VudGltZW50PXVuaXF1ZShkYXRfdGlkeS50cmFpbi5zcGVha2VyJHNlbnRpbWVudCkpCgoKZGF0X3RpZHkudHJhaW4uc3BlYWtlci5wcmVkX2dyaWQuYWxsX3NlbnQgPC0gZGF0X3RpZHkudHJhaW4uc3BlYWtlci5wcmVkX2dyaWQuYWxsX3NlbnQgJT4lCiAgbGVmdF9qb2luKGRhdGVfZ3JpZCkgJT4lCiAgbXV0YXRlKGltcGVhY2htZW50XzEgPSBpZmVsc2UoZGF0ZSA9PSBhc19kYXRldGltZSgnMTk5OC0xMC0wOCcpLCAxLCAwKSwKICAgICAgICAgaW1wZWFjaG1lbnRfMiA9IGlmZWxzZShkYXRlID09IGFzX2RhdGV0aW1lKCcxOTk4LTEyLTE5JyksIDEsIDApKQoKCnRyYWluLnNwZWFrZXIuYWxsLm0xLnByZWQgPC0gZGF0X3RpZHkudHJhaW4uc3BlYWtlci5wcmVkX2dyaWQuYWxsX3NlbnQgJT4lCiAgbXV0YXRlKHByZWRzID0gcHJlZGljdCh0cmFpbi5zcGVha2VyLmFsbC5tMSwgCiAgICAgICAgICAgICAgICAgICAgICAgICBuZXdkYXRhPWRhdF90aWR5LnRyYWluLnNwZWFrZXIucHJlZF9ncmlkLmFsbF9zZW50LCAKICAgICAgICAgICAgICAgICAgICAgICAgIGFsbG93Lm5ldy5sZXZlbHM9VCkpCiAgCgpgYGAKCgoKYGBge3IsIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD02fQp0cmFpbi5zcGVha2VyLmFsbC5tMS5wcmVkICU+JQogIGxlZnRfam9pbihkYXRlX2dyaWQpICU+JQogIGdncGxvdChhZXMoeCA9IGRhdGUsIHkgPSBwcmVkcywgY29sb3I9UGFydHkpKSArIAogIGdlb21fbGluZSgpICsgCiAgZ2VvbV9wb2ludChhZXMoeSA9IHByZWRzKSwgYWxwaGE9LjI1KSArCiAgdGhlbWVfYXBhKCkgKyAKICBzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcyA9IGMoImJsdWUiLCAicmVkIikpICsgCiAgdGhlbWVfYXBhKCkgKwogIGdlb21fdmxpbmUoeGludGVyY2VwdD1hcy5udW1lcmljKGFzX2RhdGV0aW1lKCcxOTk4LTEwLTA4JykpLCBsaW5ldHlwZT0yLCBhbHBoYT0uMjUpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQ9YXMubnVtZXJpYyhhc19kYXRldGltZSgnMTk5OC0xMi0xOScpKSwgbGluZXR5cGU9MiwgYWxwaGE9LjI1KSArCiAgZ2d0aXRsZSgnRGFpbHkgZXhwZWN0ZWQgcHJvcG9ydGlvbiBvZiBkaXNndXN0IGxhbmd1YWdlIGZvciBhbiBhdmVyYWdlIHNwZWFrZXInICkgKwogIHlsYWIoJ1NwZWFrZXIgcHJvcG9ydGlvbiBkaXNndXN0IGxhbmd1YWdlJykgKyAKICB4bGFiKCdEYXRlJykgKyBmYWNldF93cmFwKHNlbnRpbWVudH4uLCBuY29sPTIpCgpgYGAKCgoKYGBge3IsIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD02fQp0cmFpbi5zcGVha2VyLmFsbC5tMS5wcmVkICU+JQogIGxlZnRfam9pbihkYXRlX2dyaWQpICU+JQogIGdncGxvdChhZXMoeCA9IGRhdGUsIHkgPSBwcmVkcywgY29sb3I9UGFydHkpKSArIAogIGdlb21fbGluZSgpICsgCiAgZ2VvbV9wb2ludChhZXMoeSA9IHByZWRzKSwgYWxwaGE9LjI1KSArCiAgdGhlbWVfYXBhKCkgKyAKICBzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcyA9IGMoImJsdWUiLCAicmVkIikpICsgCiAgdGhlbWVfYXBhKCkgKwogIGdlb21fdmxpbmUoeGludGVyY2VwdD1hcy5udW1lcmljKGFzX2RhdGV0aW1lKCcxOTk4LTEwLTA4JykpLCBsaW5ldHlwZT0yLCBhbHBoYT0uMjUpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQ9YXMubnVtZXJpYyhhc19kYXRldGltZSgnMTk5OC0xMi0xOScpKSwgbGluZXR5cGU9MiwgYWxwaGE9LjI1KSArCiAgZ2d0aXRsZSgnRGFpbHkgZXhwZWN0ZWQgcHJvcG9ydGlvbiBvZiBkaXNndXN0IGxhbmd1YWdlIGZvciBhbiBhdmVyYWdlIHNwZWFrZXInICkgKwogIHlsYWIoJ1NwZWFrZXIgcHJvcG9ydGlvbiBkaXNndXN0IGxhbmd1YWdlJykgKyAKICB4bGFiKCdEYXRlJykgKyBmYWNldF93cmFwKHNlbnRpbWVudH4uLCBuY29sPTIsIHNjYWxlcz0nZnJlZV95JykKCgpgYGAKCgoKYGBge3J9CnNqUGxvdDo6cGxvdF9tb2RlbCh0cmFpbi5zcGVha2VyLmFsbC5tMSwgdHlwZT0ncmUnKVszXQpgYGAKCgojIyBDb25maXJtYXRpb24gCgoKYGBge3J9CgpkYXRfdGlkeS50ZXN0LnNwZWFrZXIgPC0gZGF0X3RpZHkudGVzdCAlPiUKICBncm91cF9ieShQYXJ0eSwgc3BlYWtlcikgJT4lCiAgbXV0YXRlKHNwZWFrZXJfdG90YWxfd29yZHMgPSBuKCkpICU+JQogIHVuZ3JvdXAoKSAlPiUKICBpbm5lcl9qb2luKG5yY19zZW50KSAlPiUKICBjb3VudChQYXJ0eSwgc3BlYWtlciwgc3BlYWtlcl90b3RhbF93b3JkcywgZGF0ZSwgc2VudGltZW50KSAlPiUKICBncm91cF9ieShzcGVha2VyLCBzZW50aW1lbnQpCiAgCmBgYAoKCgpgYGB7cn0KCmRhdF90aWR5LnRlc3Quc3BlYWtlciA8LSBkYXRfdGlkeS50ZXN0ICU+JQogIGZpbHRlcihQYXJ0eSAhPSAnSW5kZXBlbmRlbnQnKSAlPiUKICBncm91cF9ieShQYXJ0eSwgc3BlYWtlciwgZGF0ZSkgJT4lCiAgbXV0YXRlKHNwZWFrZXJfZGF5X24gPSBuKCkpICU+JQogIHVuZ3JvdXAoKSAlPiUKICBpbm5lcl9qb2luKG5yY19zZW50KSAlPiUKICBjb3VudChQYXJ0eSwgc3BlYWtlciwgZGF0ZSwgc3BlYWtlcl9kYXlfbiwgc2VudGltZW50KSAlPiUKICBtdXRhdGUoc3BlYWtlcl9kYXlfc2VudF9wcm9wID0gbi9zcGVha2VyX2RheV9uKQoKCgoKIyBDcmVhdGUgYSBkYXRlIHJhbmdlIGZyb20gdGhlIG1pbi9tYXggZGF0ZXMgaW4gb3VyIHRlc3RpbmcgZGF0YQpkYXRlX2dyaWQgPC0gdGliYmxlKGRhdGUgPSBzZXEobWluKGRhdF90aWR5LnRlc3Quc3BlYWtlciRkYXRlKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXgoZGF0X3RpZHkudGVzdC5zcGVha2VyJGRhdGUpLCBieT0nZGF5cycpKSAlPiUKICBtdXRhdGUoZGF0ZV9pbnQgPSByb3dfbnVtYmVyKCksICMgQXNzb2NpYXRlIGVhY2ggZGF0ZSB3aXRoIGFuIGludGVnZXIgCiAgICAgICAgIGRhdGVfaW50X3NjYWxlZCA9IGRhdGVfaW50LzEwMCkKCmRhdF90aWR5LnRlc3Quc3BlYWtlciA8LSBkYXRfdGlkeS50ZXN0LnNwZWFrZXIgJT4lCiAgbGVmdF9qb2luKGRhdGVfZ3JpZCkgJT4lCiAgbXV0YXRlKGRhdGVfaW50X3NjYWxlZCA9IGRhdGVfaW50LzEwMCwgCiAgICAgICAgIGltcGVhY2htZW50XzEgPSBpZmVsc2UoZGF0ZSA9PSBhc19kYXRldGltZSgnMTk5OC0xMC0wOCcpLCAxLCAwKSwKICAgICAgICAgaW1wZWFjaG1lbnRfMiA9IGlmZWxzZShkYXRlID09IGFzX2RhdGV0aW1lKCcxOTk4LTEyLTE5JyksIDEsIDApKQogIApgYGAKCgpgYGB7cn0KCnRlc3Quc3BlYWtlci5uZWdhdGl2ZS5tMSA8LSBkYXRfdGlkeS50ZXN0LnNwZWFrZXIgJT4lCiAgZmlsdGVyKHNlbnRpbWVudD09J25lZ2F0aXZlJykgJT4lCiAgbG1lcihzcGVha2VyX2RheV9zZW50X3Byb3AgfiAxICsgUGFydHkqaW1wZWFjaG1lbnRfMSArIFBhcnR5KmltcGVhY2htZW50XzIgICsgKDEgIHwgc3BlYWtlcikgKyAoMSArIFBhcnR5IHwgZGF0ZV9pbnQpLCBkYXRhPS4pCgpzdW1tYXJ5KHRlc3Quc3BlYWtlci5uZWdhdGl2ZS5tMSkKCmBgYAoKCgpgYGB7cn0KCgpkYXRfdGlkeS50ZXN0LnNwZWFrZXIucHJlZF9ncmlkIDwtIGV4cGFuZC5ncmlkKFBhcnR5ID0gdW5pcXVlKGRhdF90aWR5LnRlc3Quc3BlYWtlciRQYXJ0eSksIAogICAgICAgICAgICBzcGVha2VyID0gJ25ld19zcGVha2VyJywgCiAgICAgICAgICAgIGRhdGVfaW50ID0gdW5pcXVlKGRhdF90aWR5LnRlc3Quc3BlYWtlciRkYXRlX2ludCkpCgoKZGF0X3RpZHkudGVzdC5zcGVha2VyLnByZWRfZ3JpZCA8LSBkYXRfdGlkeS50ZXN0LnNwZWFrZXIucHJlZF9ncmlkICU+JQogIGxlZnRfam9pbihkYXRlX2dyaWQpICU+JQogIG11dGF0ZShpbXBlYWNobWVudF8xID0gaWZlbHNlKGRhdGUgPT0gYXNfZGF0ZXRpbWUoJzE5OTgtMTAtMDgnKSwgMSwgMCksCiAgICAgICAgIGltcGVhY2htZW50XzIgPSBpZmVsc2UoZGF0ZSA9PSBhc19kYXRldGltZSgnMTk5OC0xMi0xOScpLCAxLCAwKSkKCgoKdGVzdC5zcGVha2VyLm5lZ2F0aXZlLm0xLnByZWQgPC0gZGF0X3RpZHkudGVzdC5zcGVha2VyLnByZWRfZ3JpZCAlPiUKICBtdXRhdGUocHJlZHMgPSBwcmVkaWN0KHRlc3Quc3BlYWtlci5uZWdhdGl2ZS5tMSwgbmV3ZGF0YT1kYXRfdGlkeS50ZXN0LnNwZWFrZXIucHJlZF9ncmlkLCBhbGxvdy5uZXcubGV2ZWxzPVQpKQogIAoKCnRlc3Quc3BlYWtlci5uZWdhdGl2ZS5tMS5wcmVkICU+JQogIGxlZnRfam9pbihkYXRlX2dyaWQpICU+JQogIGdncGxvdChhZXMoeCA9IGRhdGUsIHkgPSBwcmVkcywgY29sb3I9UGFydHkpKSArIAogIGdlb21fbGluZSgpICsgCiAgZ2VvbV9wb2ludChhZXMoeSA9IHByZWRzKSwgYWxwaGE9LjI1KSArCiAgdGhlbWVfYXBhKCkgKyAKICBzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcyA9IGMoImJsdWUiLCAicmVkIikpICsgCiAgdGhlbWVfYXBhKCkgKwogIGdlb21fdmxpbmUoeGludGVyY2VwdD1hcy5udW1lcmljKGFzX2RhdGV0aW1lKCcxOTk4LTEwLTA4JykpLCBsaW5ldHlwZT0yLCBhbHBoYT0uMjUpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQ9YXMubnVtZXJpYyhhc19kYXRldGltZSgnMTk5OC0xMi0xOScpKSwgbGluZXR5cGU9MiwgYWxwaGE9LjI1KSArCiAgZ2d0aXRsZSgnRGFpbHkgZXhwZWN0ZWQgcHJvcG9ydGlvbiBvZiBuZWdhdGl2ZSBsYW5ndWFnZSBmb3IgYW4gYXZlcmFnZSBzcGVha2VyJyApICsKICB5bGFiKCdTcGVha2VyIHByb3BvcnRpb24gbmVnYXRpdmUgbGFuZ3VhZ2UnKSArIAogIHhsYWIoJ0RhdGUnKQoKICAKYGBgCgoKCiMjIERpc2d1c3QgCgpgYGB7cn0KCgp0ZXN0LnNwZWFrZXIuZGlzZ3VzdC5tMSA8LSBkYXRfdGlkeS50ZXN0LnNwZWFrZXIgJT4lCiAgZmlsdGVyKHNlbnRpbWVudD09J2Rpc2d1c3QnKSAlPiUKICBsbWVyKHNwZWFrZXJfZGF5X3NlbnRfcHJvcCB+IDEgKyBQYXJ0eSppbXBlYWNobWVudF8xICsgUGFydHkqaW1wZWFjaG1lbnRfMiAgKyAoMSAgfCBzcGVha2VyKSArICgxICsgUGFydHkgfCBkYXRlX2ludCksIGRhdGE9LikKCnN1bW1hcnkodGVzdC5zcGVha2VyLmRpc2d1c3QubTEpCgoKCnRlc3Quc3BlYWtlci5kaXNndXN0Lm0xLnByZWQgPC0gZGF0X3RpZHkudGVzdC5zcGVha2VyLnByZWRfZ3JpZCAlPiUKICBtdXRhdGUocHJlZHMgPSBwcmVkaWN0KHRlc3Quc3BlYWtlci5kaXNndXN0Lm0xLCBuZXdkYXRhPWRhdF90aWR5LnRlc3Quc3BlYWtlci5wcmVkX2dyaWQsIGFsbG93Lm5ldy5sZXZlbHM9VCkpCiAgCgoKdGVzdC5zcGVha2VyLmRpc2d1c3QubTEucHJlZCAlPiUKICBsZWZ0X2pvaW4oZGF0ZV9ncmlkKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBkYXRlLCB5ID0gcHJlZHMsIGNvbG9yPVBhcnR5KSkgKyAKICBnZW9tX2xpbmUoKSArIAogIGdlb21fcG9pbnQoYWVzKHkgPSBwcmVkcyksIGFscGhhPS4yNSkgKwogIHRoZW1lX2FwYSgpICsgCiAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXMgPSBjKCJibHVlIiwgInJlZCIpKSArIAogIHRoZW1lX2FwYSgpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQ9YXMubnVtZXJpYyhhc19kYXRldGltZSgnMTk5OC0xMC0wOCcpKSwgbGluZXR5cGU9MiwgYWxwaGE9LjI1KSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0PWFzLm51bWVyaWMoYXNfZGF0ZXRpbWUoJzE5OTgtMTItMTknKSksIGxpbmV0eXBlPTIsIGFscGhhPS4yNSkgKwogIGdndGl0bGUoJ0RhaWx5IGV4cGVjdGVkIHByb3BvcnRpb24gb2YgZGlzZ3VzdCBsYW5ndWFnZSBmb3IgYW4gYXZlcmFnZSBzcGVha2VyJyApICsKICB5bGFiKCdTcGVha2VyIHByb3BvcnRpb24gZGlzZ3VzdCBsYW5ndWFnZScpICsgCiAgeGxhYignRGF0ZScpCgoKYGBgCgoKCmBgYHtyfQoKCgp0ZXN0LnNwZWFrZXIuYWxsLm0xIDwtIGRhdF90aWR5LnRlc3Quc3BlYWtlciAlPiUKICBsbWVyKHNwZWFrZXJfZGF5X3NlbnRfcHJvcCB+IDEgKyBQYXJ0eSppbXBlYWNobWVudF8xICsgUGFydHkqaW1wZWFjaG1lbnRfMiAgKyAoMSB8IHNwZWFrZXIpICsgKDEgKyBQYXJ0eSB8IGRhdGVfaW50KSArICgxICsgaW1wZWFjaG1lbnRfMSAgKyBpbXBlYWNobWVudF8yIHwgc2VudGltZW50KSwgZGF0YT0uKQoKc3VtbWFyeSh0ZXN0LnNwZWFrZXIuYWxsLm0xKQoKCmRhdF90aWR5LnRlc3Quc3BlYWtlci5wcmVkX2dyaWQuYWxsX3NlbnQgPC0gZXhwYW5kLmdyaWQoUGFydHkgPSB1bmlxdWUoZGF0X3RpZHkudGVzdC5zcGVha2VyJFBhcnR5KSwgCiAgICAgICAgICAgIHNwZWFrZXIgPSAnbmV3X3NwZWFrZXInLCAKICAgICAgICAgICAgZGF0ZV9pbnQgPSB1bmlxdWUoZGF0X3RpZHkudGVzdC5zcGVha2VyJGRhdGVfaW50KSwKICAgICAgICAgICAgc2VudGltZW50PXVuaXF1ZShkYXRfdGlkeS50ZXN0LnNwZWFrZXIkc2VudGltZW50KSkKCgpkYXRfdGlkeS50ZXN0LnNwZWFrZXIucHJlZF9ncmlkLmFsbF9zZW50IDwtIGRhdF90aWR5LnRlc3Quc3BlYWtlci5wcmVkX2dyaWQuYWxsX3NlbnQgJT4lCiAgbGVmdF9qb2luKGRhdGVfZ3JpZCkgJT4lCiAgbXV0YXRlKGltcGVhY2htZW50XzEgPSBpZmVsc2UoZGF0ZSA9PSBhc19kYXRldGltZSgnMTk5OC0xMC0wOCcpLCAxLCAwKSwKICAgICAgICAgaW1wZWFjaG1lbnRfMiA9IGlmZWxzZShkYXRlID09IGFzX2RhdGV0aW1lKCcxOTk4LTEyLTE5JyksIDEsIDApKQoKCnRlc3Quc3BlYWtlci5hbGwubTEucHJlZCA8LSBkYXRfdGlkeS50ZXN0LnNwZWFrZXIucHJlZF9ncmlkLmFsbF9zZW50ICU+JQogIG11dGF0ZShwcmVkcyA9IHByZWRpY3QodGVzdC5zcGVha2VyLmFsbC5tMSwgCiAgICAgICAgICAgICAgICAgICAgICAgICBuZXdkYXRhPWRhdF90aWR5LnRlc3Quc3BlYWtlci5wcmVkX2dyaWQuYWxsX3NlbnQsIAogICAgICAgICAgICAgICAgICAgICAgICAgYWxsb3cubmV3LmxldmVscz1UKSkKICAKCmBgYAoKCgpgYGB7ciwgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTZ9CnRlc3Quc3BlYWtlci5hbGwubTEucHJlZCAlPiUKICBsZWZ0X2pvaW4oZGF0ZV9ncmlkKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBkYXRlLCB5ID0gcHJlZHMsIGNvbG9yPVBhcnR5KSkgKyAKICBnZW9tX2xpbmUoKSArIAogIGdlb21fcG9pbnQoYWVzKHkgPSBwcmVkcyksIGFscGhhPS4yNSkgKwogIHRoZW1lX2FwYSgpICsgCiAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXMgPSBjKCJibHVlIiwgInJlZCIpKSArIAogIHRoZW1lX2FwYSgpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQ9YXMubnVtZXJpYyhhc19kYXRldGltZSgnMTk5OC0xMC0wOCcpKSwgbGluZXR5cGU9MiwgYWxwaGE9LjI1KSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0PWFzLm51bWVyaWMoYXNfZGF0ZXRpbWUoJzE5OTgtMTItMTknKSksIGxpbmV0eXBlPTIsIGFscGhhPS4yNSkgKwogIGdndGl0bGUoJ0RhaWx5IGV4cGVjdGVkIHByb3BvcnRpb24gb2YgZGlzZ3VzdCBsYW5ndWFnZSBmb3IgYW4gYXZlcmFnZSBzcGVha2VyJyApICsKICB5bGFiKCdTcGVha2VyIHByb3BvcnRpb24gZGlzZ3VzdCBsYW5ndWFnZScpICsgCiAgeGxhYignRGF0ZScpICsgZmFjZXRfd3JhcChzZW50aW1lbnR+LiwgbmNvbD0yKQoKYGBgCgoKCmBgYHtyLCBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9Nn0KdGVzdC5zcGVha2VyLmFsbC5tMS5wcmVkICU+JQogIGxlZnRfam9pbihkYXRlX2dyaWQpICU+JQogIGdncGxvdChhZXMoeCA9IGRhdGUsIHkgPSBwcmVkcywgY29sb3I9UGFydHkpKSArIAogIGdlb21fbGluZSgpICsgCiAgZ2VvbV9wb2ludChhZXMoeSA9IHByZWRzKSwgYWxwaGE9LjI1KSArCiAgdGhlbWVfYXBhKCkgKyAKICBzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcyA9IGMoImJsdWUiLCAicmVkIikpICsgCiAgdGhlbWVfYXBhKCkgKwogIGdlb21fdmxpbmUoeGludGVyY2VwdD1hcy5udW1lcmljKGFzX2RhdGV0aW1lKCcxOTk4LTEwLTA4JykpLCBsaW5ldHlwZT0yLCBhbHBoYT0uMjUpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQ9YXMubnVtZXJpYyhhc19kYXRldGltZSgnMTk5OC0xMi0xOScpKSwgbGluZXR5cGU9MiwgYWxwaGE9LjI1KSArCiAgZ2d0aXRsZSgnRGFpbHkgZXhwZWN0ZWQgcHJvcG9ydGlvbiBvZiBkaXNndXN0IGxhbmd1YWdlIGZvciBhbiBhdmVyYWdlIHNwZWFrZXInICkgKwogIHlsYWIoJ1NwZWFrZXIgcHJvcG9ydGlvbiBkaXNndXN0IGxhbmd1YWdlJykgKyAKICB4bGFiKCdEYXRlJykgKyBmYWNldF93cmFwKHNlbnRpbWVudH4uLCBuY29sPTIsIHNjYWxlcz0nZnJlZV95JykKCgpgYGAKCgoKYGBge3J9CnNqUGxvdDo6cGxvdF9tb2RlbCh0ZXN0LnNwZWFrZXIuYWxsLm0xLCB0eXBlPSdyZScpWzNdCmBgYAoKCgojIyMgVmFsaWRhdGluZyBkaWN0aW9uYXJpZXMgCgpPbmUgb2YgdGhlIGdyZWF0ZXN0IHN0cmVuZ3RocyBvZiBkaWN0aW9uYXJ5LWJhc2VkIHRleHQgbWVhc3VyZW1lbnQgbWV0aG9kcyBpcyB0aGF0IHRoZXkgYWxsb3cgeW91IHRvIHByZWNpc2VseSBkZWZpbmUgdGhlIGNvbnN0cnVjdCB5b3UgYXJlIGludGVyZXN0ZWQgaW4uIFRoaXMgd29ya3MgZXh0cmVtZWx5IHdlbGwgd2hlbiB5b3UgYXJlIGludGVyZXN0ZWQgaW4gc3BlY2lmaWMgd29yZHMgb3IgdHlwZXMgb2Ygd29yZHMuIAoKRm9yIGV4YW1wbGUsIGlmIHlvdSBhcmUgaW50ZXJlc3RlZCBpbiAqZnVuY3Rpb24gd29yZHMqLCB0aGVuIGl0IHdvdWxkIG5ldmVyIG1ha2Ugc2Vuc2UgdG8gdXNlIGFueXRoaW5nIG90aGVyIHRoYW4gYSBkaWN0aW9uYXJ5LWJhc2VkIGFwcHJvYWNoLiBTaW1pbGFybHksIGlmIHlvdSBhcmUgdHJ1bHkgaW50ZXJlc3RlZCBpbiB0aGUgdXNhZ2Ugb2YgKnBvc2l0aXZlKiBvciAqbmVnYXRpdmUqIHdvcmRzLCB0aGVuLCBhZ2FpbiwgaXQgcHJvYmFibHkgd291bGRuJ3QgbWFrZSBzZW5zZSB0byB1c2UgYW55dGhpbmcgb3RoZXIgdGhhbiBhIGRpY3Rpb25hcnkgYXBwcm9hY2guIAoKSG93ZXZlciwgCg==